Skip to content

Commit d3ec7e0

Browse files
committed
feat: custom ARC runner image with C toolchain
Thin layer on the official ghcr.io/actions/actions-runner base, adding build-essential so cgo builds (go test -race) work without a per-job apt-get. Includes a build+test+publish workflow, Dependabot for base-image and action-SHA bumps, and operator docs for ARC Helm consumption.
0 parents  commit d3ec7e0

4 files changed

Lines changed: 180 additions & 0 deletions

File tree

.github/dependabot.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: docker
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
day: monday
8+
time: "04:00"
9+
timezone: Europe/Berlin
10+
commit-message:
11+
prefix: build
12+
- package-ecosystem: github-actions
13+
directory: /
14+
schedule:
15+
interval: weekly
16+
day: monday
17+
time: "04:00"
18+
timezone: Europe/Berlin
19+
commit-message:
20+
prefix: ci

.github/workflows/build.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: build runner image
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- Dockerfile
8+
- .github/workflows/build.yml
9+
pull_request:
10+
paths:
11+
- Dockerfile
12+
- .github/workflows/build.yml
13+
schedule:
14+
- cron: "37 4 * * 1" # weekly Mon ~04:37 UTC
15+
workflow_dispatch:
16+
17+
concurrency:
18+
group: build-${{ github.ref }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
build:
23+
runs-on: ubuntu-latest
24+
permissions:
25+
contents: read
26+
packages: write
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
30+
31+
- name: Set up Docker Buildx
32+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
33+
34+
- name: Log in to GHCR
35+
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
36+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
37+
with:
38+
registry: ghcr.io
39+
username: ${{ github.actor }}
40+
password: ${{ secrets.GITHUB_TOKEN }}
41+
42+
- name: Derive runner version from Dockerfile
43+
id: ver
44+
run: |
45+
version="$(sed -nE 's/^FROM[[:space:]]+ghcr\.io\/actions\/actions-runner:([0-9]+\.[0-9]+\.[0-9]+)([[:space:]]|$).*/\1/p' Dockerfile | head -n1 || true)"
46+
if [ -z "${version}" ]; then
47+
echo "::error::could not parse runner version from Dockerfile FROM line"
48+
exit 1
49+
fi
50+
echo "runner_version=${version}" >> "$GITHUB_OUTPUT"
51+
52+
- name: Docker metadata
53+
id: meta
54+
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
55+
with:
56+
images: ghcr.io/${{ github.repository }}
57+
tags: |
58+
type=raw,value=latest,enable={{is_default_branch}}
59+
type=raw,value=${{ steps.ver.outputs.runner_version }},enable={{is_default_branch}}
60+
type=sha,format=long
61+
62+
- name: Build and load locally
63+
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
64+
with:
65+
context: .
66+
load: true
67+
tags: ${{ steps.meta.outputs.tags }}
68+
labels: ${{ steps.meta.outputs.labels }}
69+
platforms: linux/amd64
70+
71+
- name: Smoke test
72+
env:
73+
TAGS: ${{ steps.meta.outputs.tags }}
74+
run: |
75+
ref="$(printf '%s\n' "${TAGS}" | head -n1)"
76+
docker run --rm --entrypoint bash "${ref}" -lc '
77+
set -euo pipefail
78+
test "$(id -un)" = runner
79+
command -v gcc
80+
command -v cc
81+
command -v docker
82+
test -x "$HOME/run.sh"
83+
'
84+
85+
- name: Push to GHCR
86+
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
87+
env:
88+
TAGS: ${{ steps.meta.outputs.tags }}
89+
run: |
90+
set -euo pipefail
91+
test -n "${TAGS}"
92+
printf '%s\n' "${TAGS}" | while IFS= read -r tag; do
93+
[ -n "${tag}" ] || continue
94+
docker push "${tag}"
95+
done

Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Custom GitHub Actions self-hosted runner image for airplanes.live ARC.
2+
# Official runner image + a C toolchain so cgo builds (e.g. `go test -race`)
3+
# work without a per-job apt-get. Docker-in-Docker for image builds is a
4+
# separate concern, handled by `containerMode: dind` in the ARC Helm values,
5+
# not by this image.
6+
FROM ghcr.io/actions/actions-runner:2.334.0
7+
8+
USER root
9+
RUN apt-get update \
10+
&& apt-get install -y --no-install-recommends \
11+
build-essential \
12+
&& rm -rf /var/lib/apt/lists/*
13+
USER runner

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# actions-runner
2+
3+
Custom GitHub Actions self-hosted runner image for airplanes.live ARC.
4+
5+
Thin layer on top of the official [`ghcr.io/actions/actions-runner`](https://github.com/actions/runner/pkgs/container/actions-runner) base, adding a C toolchain (`build-essential`) so cgo builds (`go test -race`) work without a per-job `apt-get`. The base image ships Docker CLI + Buildx; those are untouched.
6+
7+
We don't use a community runner image (e.g. `catthehacker/ubuntu`) — single-maintainer supply-chain exposure. A pinned Dockerfile on GitHub's official base keeps us on a patched image we control.
8+
9+
## Consuming it in ARC
10+
11+
Two independent knobs need to be set in the `gha-runner-scale-set` Helm values:
12+
13+
1. **Runner image** — the toolchain this repo provides.
14+
2. **`containerMode: dind`** — a Docker-in-Docker sidecar that provides a Docker daemon for image builds (the runner image only has the client). This is separate from the image.
15+
16+
```yaml
17+
template:
18+
spec:
19+
nodeSelector:
20+
kubernetes.io/arch: amd64
21+
imagePullSecrets:
22+
- name: ghcr-pull # see "GHCR access" below
23+
containers:
24+
- name: runner # ARC requires this name
25+
image: ghcr.io/airplanes-live/actions-runner:2.334.0
26+
imagePullPolicy: Always # tag is mutable across rebuilds
27+
command: ["/home/runner/run.sh"]
28+
containerMode:
29+
type: dind # Docker daemon sidecar; requires privileged pods
30+
```
31+
32+
**Notes:**
33+
34+
- `dind` requires privileged pods — may be blocked by Pod Security Admission or policy engines. Verify the cluster allows it before rolling out.
35+
- If the dind sidecar itself needs customisation (image, security context), `containerMode` must be replaced with a full pod spec per the [ARC docs](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/deploying-runner-scale-sets-with-actions-runner-controller#using-docker-in-docker-mode).
36+
37+
## GHCR access
38+
39+
This package is private. ARC pods need an `imagePullSecret` whose token has `read:packages` scope — anonymous pulls fail for private GHCR packages.
40+
41+
Preflight: `docker pull ghcr.io/airplanes-live/actions-runner:<tag>` from a cluster node using the same credentials before switching Helm values.
42+
43+
## Updates
44+
45+
[Dependabot](.github/dependabot.yml) opens PRs weekly:
46+
47+
- **`docker` ecosystem** bumps the `FROM` runner version when GitHub cuts a new release.
48+
- **`github-actions` ecosystem** bumps the pinned action SHAs in the build workflow.
49+
50+
PR builds validate the image (build + smoke test) without publishing. Merging to `main` triggers a build that publishes to GHCR.
51+
52+
GitHub requires self-hosted runners to stay within ~30 days of the latest release — treat runner-version Dependabot PRs as time-sensitive.

0 commit comments

Comments
 (0)