Gitea Act Runner (DinD + cache)
Stable Gitea Actions runner for building Rust projects. Runs jobs in an isolated Docker-in-Docker daemon with the built-in cache server enabled and multiple parallel jobs.
Setup on a new build machine
Requires Docker + Docker Compose.
git clone https://git.vysma.dev/nampdn/act_runner.git && cd act_runner
cp .env.example .env
# edit .env: set GITEA_RUNNER_NAME (unique per machine) and a fresh
# GITEA_RUNNER_REGISTRATION_TOKEN from Gitea -> Actions -> Runners -> Create Runner
docker compose up -d
Nothing else to edit — config.yaml ships with working defaults. Tweak
runner.capacity per machine if its CPU/RAM differ; that edit will show up as a
local modification in git status, which is expected.
Verify:
docker compose ps # both containers up, dind healthy
docker logs gitea-runner-wsl 2>&1 | tail # "declare successfully"
Config
-
Parallel jobs:
config.yaml->runner.capacity(default 4) -
Job images:
config.yaml->runner.labels(ubuntu-latest/22.04/24.04) -
Cache:
config.yaml->cache.dir: "/cache", mounted from therunner-cachevolume. Docker layer cache persists separately indind-storage.⚠️ Leaving
cache.dirempty makes act_runner write to$HOME/.cache/actcache— the container's writable layer, not a volume. Measured in production: 278 GB / 95 GB / 32 GB of cache sitting there across three machines, all of it destroyed by a singledocker compose up -d. The two settings (cache.dirand therunner-cachemount) must always be changed together. -
Runner image: pinned by digest, not
:nightly. A floating tag meansdocker compose pullcan recreate the container behind your back — and with it, wipe the cache. Upgrade deliberately: change the digest, commit, then apply.
Rust workflows
Copy example-rust-workflow.yaml to your repo at .gitea/workflows/build.yaml.
It uses actions-rust-lang/setup-rust-toolchain which caches ~/.cargo and
target/ via the runner's cache server.
Manage
docker compose up -d # apply config changes
docker compose down # stop (registration persists in volume)
⚠️ Check for running jobs before recreating the runner. Recreating it orphans any job in flight — Gitea marks them failed and you have to re-run:
docker exec gitea-runner-dind docker ps --format '{{.Names}}' | grep -v buildkit
Wait until that prints nothing (buildx_buildkit_builder-* is a helper, not a job).
Housekeeping
Job images, volumes and BuildKit cache pile up inside DinD and are never reaped automatically. Measured on one machine: 515 unused images and a 71 GB build cache.
docker exec gitea-runner-dind docker image prune -af --filter until=168h
docker exec gitea-runner-dind docker builder prune -f --keep-storage 20GB
# volume prune only removes *anonymous* volumes; task volumes are named:
for v in $(docker exec gitea-runner-dind docker volume ls -q | grep '^GITEA-ACTIONS-TASK'); do
docker exec gitea-runner-dind docker volume rm "$v" 2>/dev/null
done
Keep act-toolcache and don't wipe the BuildKit cache outright — cap it instead.
Each machine needs a unique
GITEA_RUNNER_NAMEand its own registration token. Rundocker compose down -vto fully reset (drops registration + caches).