Custom runner image forARC
The custom Actions Runner Controller image consumed by the arc-runner-set scale set in the arc-runners namespace. It layers a small bundle of build tooling on top of ghcr.io/actions/actions-runner so CI jobs do not have to apt-get install the same packages on every cold start.
One base, every tool baked
Layered onto ghcr.io/actions/actions-runner so every tool the runner pool needs is on PATH the moment the pod starts — no cold-start apt-get, air-gap-safe under mirror outages, one source of truth for tool versions.
- git-lfs / unzip / jq / gh — workflow shell glue.
- kubectl / dbmate — pinned via build args for parity.
What it gives you
Features
No cold-start apt-get
Every tool the runner pool needs is on PATH the moment the runner pod starts, instead of being apt-get installed as the first step of every job.
Air-gap-safe
Image dependencies are pinned via the upstream actions-runner tag plus apt snapshots, so a transient archive.ubuntu.com outage no longer fails Unity matrix legs.
Single source of truth
Bumping a tool happens once in this Dockerfile + version, not in every workflow that references it.
What's baked in
Image layout & build args
arc-runner is the custom Actions Runner Controller image consumed by the arc-runner-set scale set in the arc-runners namespace. It layers a small bundle of build tooling on top of ghcr.io/actions/actions-runner so CI jobs do not have to apt-get install the same packages on every cold start.
Why a custom image
Workflows that run on arc-runner-set currently install git-lfs, unzip, and similar tools as the first step of every job. The Unity build matrix in ci-unity.yml re-installs git-lfs once per build target — three installs per workflow run today, more as we add Unity targets. Cumulatively this adds minutes of wall-clock time to every Unity matrix and burns ARC pod CPU on apt repo round-trips that are identical across runs.
A baked image fixes the pattern in three ways:
- No cold-start
apt-get— every tool the runner pool needs is onPATHthe moment the runner pod starts. - Air-gap-safe under apt mirror outages — image dependencies are pinned via the upstream
actions-runnertag plusaptsnapshots; a transientarchive.ubuntu.comoutage no longer fails Unity matrix legs. - Single source of truth — bumping a tool happens once in this Dockerfile + version, not in every workflow that references it.
Image layout
| Binary | Purpose |
|---|---|
actions-runner | Upstream GitHub Actions self-hosted runner (/home/runner/run.sh) |
git-lfs | Forgejo LFS pulls (rareicon Unity, future chuck UE5) |
unzip | butler installer + generic archive tooling |
jq | Manifest dispatch + workflow shell glue |
xz-utils | .tar.xz extraction (Unity, butler, SDK installers) |
gh | GitHub CLI for workflows that shell out to it |
curl | Inherited from upstream, kept explicit for downstream callers |
ca-certificates | TLS trust for HTTPS-fetched dependencies |
rsync | Cross-host file sync (Unity / UE asset shuffling) |
build-essential + pkg-config | Native-build deps for crates that compile C/C++ on the runner (ci-uniti, ci-dashboard) |
protobuf-compiler + libprotobuf-dev | protoc for crates that codegen from .proto (used six times across ci-uniti) |
gettext-base (envsubst) | k8s manifest rendering (ci-dbmate-deploy) |
postgresql-client (psql) | Seed/init step in ci-dbmate-validate |
kubectl | Pinned k8s client used by ci-dbmate-deploy; version controlled by KUBECTL_VERSION build arg |
dbmate | Pinned migrator. ci-dbmate-validate runs it directly; ci-dbmate-deploy invokes it via the in-cluster Job (image bake = parity); version controlled by DBMATE_VERSION build arg |
The image runs as the upstream runner user by default. The pod spec sets runAsUser: 0 and the entrypoint hands control to /home/runner/run.sh; a sudoers entry for root is baked in so a future cleanup can drop the inline command: override on arc-runner-set.
Build args
| Arg | Default | Purpose |
|---|---|---|
ACTIONS_RUNNER_VERSION | 2.335.1 | Upstream ghcr.io/actions/actions-runner tag the image is layered onto. Bump to follow upstream minor releases. |
KUBECTL_VERSION | 1.31.0 | Pinned kubectl release. SHA verified against dl.k8s.io/release/v<KUBECTL_VERSION>/bin/linux/amd64/kubectl.sha256 at build. |
DBMATE_VERSION | 2.28.0 | Pinned dbmate release. Tracks the in-cluster ci-dbmate-deploy Job image for parity. |
Migration
Used by, rollout & cadence
Used by
kube/github/runners/manifests/values.yaml—template.spec.containers[].imageandtemplate.spec.initContainers[].imagewill flip toghcr.io/kbve/arc-runner:<tag>in a follow-up PR. The first publish of this image happens on the upstream runner image; subsequent rebuilds run on the custom image itself.
Rollout sequence
This page tracks the image artefact only. The full migration runs across three PRs to keep rollback cheap:
- First publish —
ghcr.io/kbve/arc-runner:0.1.1via the standardci-docker.ymldispatch path. The:latesttag is built locally; CI promotes theci-{sha}build to:<mdx-version>at publish time, so the version in arc-runner.mdx frontmatter is the source of truth —project.jsondoes not hardcode it. - Follow-up — flip both
image:references inarc-runner-setvalues.yaml and bump thekbve.com/restart-triggerannotation. - Follow-up — strip the
Install git-lfsstep (and any otherapt-get installband-aids) fromci-unity.ymlonce the new runner pods are confirmed serving jobs.
Version cadence
- Track upstream
actions/runnerminor releases inACTIONS_RUNNER_VERSION. Patch bumps can ride along on dependabot. - Every image publish bumps both
version.toml(rebuild trigger via the dispatch manifest) and thekbve.com/restart-triggerannotation in values.yaml (forces a runner pod roll).
Questions
Frequently asked
What is the arc-runner image?
It is a custom Actions Runner Controller image consumed by the arc-runner-set scale set in the arc-runners namespace. It layers build tooling on top of the upstream ghcr.io/actions/actions-runner base so CI jobs do not apt-get install the same packages on every cold start.
Why bake tools into the runner instead of installing them per job?
Baking removes cold-start apt-get calls, stays air-gap-safe under apt mirror outages, and gives a single source of truth so bumping a tool happens once in the Dockerfile instead of in every workflow.
Which build args control the arc-runner image?
ACTIONS_RUNNER_VERSION pins the upstream runner tag, KUBECTL_VERSION pins the kubectl release (SHA-verified at build), and DBMATE_VERSION pins the dbmate migrator to match the in-cluster ci-dbmate-deploy Job.