Custom ARC (actions-runner-controller)
runner image with the Rust toolchain baked in. Built on top of the official
ghcr.io/actions/actions-runner image.
Image: ghcr.io/atoz-project/arc-runner-rust (public, anonymous pull works)
The scale set arc-runner-set-rust (runner group arc-public) serves this
image. Select it per job — the label is the image choice:
jobs:
test:
runs-on: arc-runner-set-rust # Rust toolchain preinstalled, no setup steps
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2 # cargo registry + target cache
- run: cargo test --locked # no toolchain setup at allPin the toolchain in your repo to match the image, so the preinstalled toolchain is a rustup no-op on the pod:
# rust-toolchain.toml
[toolchain]
channel = "1.98.1"The same file also pins GitHub-hosted runners and local checkouts, so clippy gates behave identically in all three environments. See docs/adr/0001-explicit-toolchain-pinning.md.
What you can drop compared to the default runner image:
apt-get install build-essential/ musl packages — baked indtolnay/rust-toolchain/setup-rust-toolchain— a versioned setup action on an ephemeral pod just re-downloads the toolchain. Only use one if you need a different version than the image pin, or extra components
Swatinem/rust-cache is the standard cache; don't combine it with
setup-rust-toolchain's built-in cache (double caching). python3, unzip,
git, curl, jq, sudo are inherited from the base image — don't
apt-install them either.
| Component | Version / source | Notes |
|---|---|---|
| GitHub Actions Runner | pinned base ghcr.io/actions/actions-runner |
inherits git, curl, jq, sudo |
| Rust | 1.98.1 via rustup (minimal profile) | clippy, rustfmt; targets x86_64-unknown-linux-musl, x86_64-pc-windows-gnu |
| Build tools | build-essential, musl-tools, gcc-mingw-w64-x86-64 |
via apt; binutils (readelf/strings) ships with gcc; mingw gcc is the linker driver for win-gnu cross builds |
cargo / rustc are on PATH for the runner user (~/.cargo/bin).
No Docker daemon / CLI / sidecar. Pure Rust CI (cargo build / test /
clippy) never needs a docker socket; leaving it out keeps the runner pod
unprivileged. Workflows that build images (buildx, compose) stay on
GitHub-hosted runners.
The live scale set was installed with:
helm upgrade --install arc-runner-set-rust \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
--version 0.14.2 -n arc-runners -f deploy/values.yamlTo roll out a new image build, bump the version tag in deploy/values.yaml and re-run the helm command above — pending jobs drain to the old pods automatically.
.github/workflows/build.yml builds and pushes
on every main push touching the Dockerfile and on manual dispatch. Tags:
latest, sha-<commit>, and the pinned Rust version (e.g. 1.98.1,
extracted from the Dockerfile — single source of truth). The scale set pins
the version tag; latest is never referenced by the deployment.
Explicit four-step sequence (see the ADR for why):
- Bump
--default-toolchainin the Dockerfile → merge; the build workflow pushes a new<version>tag - Bump the tag in deploy/values.yaml →
helm upgrade - Bump
rust-toolchain.tomlin each consumer repo - Until a consumer bumps, its pods download the old toolchain on demand — slower, never broken