- Dockerfile 77.7%
- Shell 22.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
publish / publish (push) Successful in 1m6s
- mise 2026.9.3 → 2026.10.13 - alpine 3.22 → 3.24 - node 24 → 26, python 3.14 → 3.15 (musl prebuilds exist for both) - actions/checkout v5 → v7 - kaniko: gcr.io/kaniko-project (archived 2025-06) → ghcr.io/osscontainertools/kaniko:v1.28.6-debug (maintained fork) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
| .forgejo/workflows | ||
| Dockerfile | ||
| mise.sh | ||
| mise.toml | ||
| README.md | ||
mise-runner
A public Forgejo Actions job
image: Alpine 3.22 with mise (v2026.9.3, musl build)
and a preinstalled toolchain — node 26, python 3.15 — plus git, curl, bash,
and a full C toolchain so mise install can build anything else from source.
GITHUB_TOKEN handling
Forgejo injects its scope-less task token as GITHUB_TOKEN into every job
container. mise's token resolution would pick it up and then 401 against
api.github.com even for public endpoints, breaking mise install for any
repo with a GitHub-hosted tool. The mise on PATH is therefore a wrapper
(mise.sh) around the real binary (mise-bin) that unsets GITHUB_TOKEN
unless:
MISE_GITHUB_TOKENis set — a real mise token, which mise prefers anywayMISE_KEEP_GITHUB_TOKEN=1— formise run/mise xchildren that call the Forgejo API with the injected token
Workflows on any repo of the same Forgejo instance select it with
runs-on: mise-latest and get mise with zero per-job setup:
on: push
jobs:
check:
runs-on: mise-latest
steps:
- uses: actions/checkout@v5
- run: mise fmt --check # tools from your repo's mise.toml
mise install in a job writes to the container's own /opt/mise-cache/data
(seeded from the image), which is discarded when the job ends. To carry tools
between jobs, cache them explicitly with actions/cache: the runner already
serves ACTIONS_CACHE_URL (see below).
- uses: actions/cache@v4
with:
path: /opt/mise-cache/data/installs
key: mise-${{ runner.os }}-${{ hashFiles('mise.toml', 'mise.lock') }}
Repos pin their own versions with a normal mise.toml; mise install only
builds what's missing. System nodejs and bash exist independently of mise,
so uses: steps and default step shells work without any mise config.
Who builds this, and where
Only two roles exist, and they happen on different machines:
- You, wherever you have Docker — build the image and push it to the Forgejo package registry. This is a one-time step per image update. Your laptop is fine; the target platform is linux/amd64, and Docker on any host (including macOS via Docker Desktop / OrbStack) can build and push that.
- The runner's dind daemon, on the server — pulls the published image
by tag when a job says
runs-on: mise-latest. It never builds anything and needs no access to your local Docker.
So: no, you don't run docker build on the server. Build locally, push, and
the server-side runner picks the image up from the registry.
Build & publish
# anywhere with Docker; hailmary is amd64, so that's the platform to build for
docker build --platform linux/amd64 \
-t src.timharek.no/tim/mise-runner:<version> \
-t src.timharek.no/tim/mise-runner:latest .
docker login src.timharek.no -u tim # PAT with package write scope
docker push src.timharek.no/tim/mise-runner:<version>
docker push src.timharek.no/tim/mise-runner:latest
Tag both <version> and latest: latest is what the runner's label points
at, but since the runner's force_pull is false, a floating tag alone makes
it ambiguous which build a job actually ran.
Then set the package's visibility to public in Forgejo (Packages → mise-runner → manage): the dind daemon pulls anonymously, and a private package fails as a confusing job error rather than an auth prompt.
Where the runner itself is configured
There is no runner daemon in this repo — only the image. The daemon is
deployed as a service in the Forgejo Dokploy app on the hailmary host,
running jobs under a docker:29-dind sidecar, with its config at
/srv/docker/forgejo/runner/config.yml (not managed here). When a new image
version is published, the only runner-side change needed is the label
pointing at it:
labels:
- "mise-latest:docker://src.timharek.no/tim/mise-runner:latest"
The same config carries the runner's job cache, which is what backs
actions/cache:
cache:
enabled: true
dir: /data/cache # on the /srv/docker/forgejo/runner bind mount
host: 192.168.16.4 # this container's IP on the compose network
proxy_port: 4000
host is not optional in practice: jobs run inside the dind sidecar, which
resolves no forgejo-runner DNS name and cannot reach the runner on its own
network. It can reach the runner's compose-network IP, which is what host
pins — auto-detection picks an interface by heuristics and is not worth
relying on. The runner then hands each job a per-run ACTIONS_CACHE_URL and
proxies to a cache server on dir, with entries scoped per repository. Nothing
prunes dir; it grows with every distinct cache key.
Files
Dockerfile— alpine:3.22 + mise (musl, verified against the release'sSHASUMS256.txt; note mise's asset isx64, notamd64) + baked toolchain.mise.toml— the image's default tool set (node 26, python 3.15).