GitHub Actions Self-Hosted Runner Setup
July 30, 2026 ยท View on GitHub
Last updated: 2026-06-27
Linux self-hosted runner (linux-lab) on controller
CI now runs the full Linux nextest lane on a self-hosted runner with labels
self-hosted and linux-lab.
Fork PRs remain on GitHub-hosted ubuntu-latest via test-fork.
Runner target label
linux-labis recognized inci.ymlas the Linux CI label.linux-labis listed in.github/actionlint.yamlso actionlint accepts the custom label.
Container setup (controller)
The runner runs as a Docker container on controller. Keep Compose files on the
cache pool, not under /opt, because controller is Unraid and /opt does not
survive reboot.
- Compose:
/mnt/cache/compose/actions-runner/lab/docker-compose.yml - Startup script:
/mnt/cache/compose/actions-runner/lab/start.sh - Runner state:
/mnt/cache/appdata/actions-runner/lab/
Runner state is on a dedicated ZFS dataset with a hard quota so CI artifacts cannot grow until they consume the whole Unraid cache pool:
zfs create -o mountpoint=/mnt/cache/appdata/actions-runner cache/appdata/actions-runner
zfs create \
-o mountpoint=/mnt/cache/appdata/actions-runner/lab \
-o quota=60G \
cache/appdata/actions-runner/lab
If the runner exceeds the quota, jobs fail with disk-full errors inside the
runner dataset instead of filling /mnt/cache.
The container uses GitHub's official runner image and JIT registration. Store a
repo-scoped PAT with runner admin permissions in
/mnt/cache/compose/actions-runner/lab/.env:
GITHUB_PAT=github_pat_or_gho_token_here
Current Compose shape:
services:
lab-linux-runner:
image: ghcr.io/actions/actions-runner:2.335.1
container_name: lab-linux-runner
restart: unless-stopped
working_dir: /home/runner
environment:
- RUNNER_REPO=dinglebear-ai/labby
- RUNNER_NAME=linux-ci-runner
- RUNNER_LABELS=linux-lab,self-hosted,linux,x64
- RUNNER_WORKDIR=/home/runner/_work
- RUNNER_URL=https://github.com/dinglebear-ai/labby
- RUNNER_USE_JIT=1
- TMPDIR=/tmp
- TMP=/tmp
- TEMP=/tmp
- RUNNER_TEMP=/home/runner/_work/_temp
env_file:
- .env
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /mnt/cache/appdata/actions-runner/lab/home:/home/runner
- /mnt/cache/appdata/actions-runner/lab/work:/home/runner/_work
- /mnt/cache/appdata/actions-runner/lab/tmp:/tmp
- /mnt/cache/compose/actions-runner/lab/start.sh:/start.sh:ro
command: ["/start.sh"]
Start or restart from the persistent Compose directory:
cd /mnt/cache/compose/actions-runner/lab
docker compose up -d
The startup script removes stale same-name remote runners, generates a JIT
config through GitHub's API, and runs ./run.sh --jitconfig .... It also keeps
container temp usage cache-backed by bind-mounting container /tmp to
/mnt/cache/appdata/actions-runner/lab/tmp, avoiding Unraid's RAM-backed host
/tmp.
The script prunes transient runner storage before each JIT registration:
/tmpdirect children: removed every startup${RUNNER_WORKDIR}/_tempdirect children: removed every startup- old workspaces in
${RUNNER_WORKDIR}: removed afterRUNNER_WORK_RETENTION_DAYS(default7) /home/runner/_diagfiles: removed afterRUNNER_DIAG_RETENTION_DAYS(default14)- cargo registry cache files and cargo git checkouts: removed after
RUNNER_CARGO_RETENTION_DAYS(default30)
The cache-backed temp directory must preserve normal /tmp semantics:
chmod 1777 /mnt/cache/appdata/actions-runner/lab/tmp
On startup, start.sh ensures the Linux build dependencies required by this
Rust workspace are installed in the runner container:
build-essentialforcc,gcc,g++, and libc headerspkg-configcmakeclangandlibclang-devnasm
Validation
cd /mnt/cache/compose/actions-runner/lab
docker compose logs -f
docker exec lab-linux-runner df -h /tmp /home/runner /home/runner/_work
docker exec lab-linux-runner sh -lc 'command -v cc pkg-config cmake clang nasm'
zfs list -o name,mountpoint,quota,used,available cache/appdata/actions-runner/lab
du -sh /mnt/cache/appdata/actions-runner/lab/*
From GitHub, confirm the runner is online with labels:
self-hostedlinux-lab
Notes
- The runner uses JIT registration. If it goes offline, restart the Compose
service;
start.shremoves the stale remote runner and registers a fresh one. - Do not bind-mount an empty directory over
/home/runnerwithout seeding the runner image contents first; that hidesrun.shandconfig.sh. - Keep this runner label in
.github/actionlint.yamland inci.ymlwhenever labels change. - If you want strict hardening, add container resource limits and restart policy controls in a systemd unit wrapping Compose.