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
Section titled “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
Section titled “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
Section titled “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
Section titled “Used by”apps/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
Section titled “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
Section titled “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.
