This image is a TOOLCHAIN, so "it built" is not the property worth checking —
"it still contains what the repo's gates shell out to" is.
It exists because the hand-built image had drifted from this Dockerfile: the
deployed ci3 carried gcc, musl-dev, python3 and py3-yaml, the Dockerfile did
not, and rebuilding from the Dockerfile silently dropped them. Gitea CI then
failed at "credentials-coverage: need python3" without reaching the tests at
all. Nothing in the pipeline objected, because every check asked whether the
image built, not what was in it.
Asserts python3 with a working yaml import, gcc, node, bash, make, curl, tar
and git-http-backend, on BOTH architectures — a package that resolves on amd64
but not arm64 would otherwise reach half the nodes unnoticed.
Verified the check can actually FAIL, which is the only property that makes it
worth having: against the bare forgejo/runner:12.11.1 it reports python3+yaml,
gcc, node and git-http-backend all MISSING; with the packages installed it
reports 3.12.14 and ok for the rest.
Publishing this image by hand kept hitting the same four walls — Cloudflare's
413, Docker parsing a bare MagicDNS name as a Docker Hub namespace, a PAT
missing write:package, and multi-arch. push-image.sh made that survivable;
this removes the manual step entirely.
It builds on GitHub rather than our own Gitea Actions because the runners
cannot build images: their labels are `ubuntu-latest:host` (host mode, no job
containers) and the container drops ALL capabilities with
allowPrivilegeEscalation:false, while rootless buildah needs newuidmap/user
namespaces for RUN steps. Building there would mean weakening the posture the
hardening established.
It publishes to ghcr.io rather than the Gitea registry because Cloudflare's
body cap is what caused the 413 in the first place, and because the cluster
already pulls zegit's own images from ghcr.io through an existing
github-registry-credentials pull secret — an established path rather than a
new one.
A master push validates that both arches still build but publishes nothing;
only an explicit dispatch names a tag and pushes, so a branch push cannot
silently mint a version. The final step reads the published index back and
fails if either architecture is absent, since a single-arch image under this
tag would CrashLoop on half the nodes.
Action SHAs match the pins zegit already uses, rather than introducing a
second set of versions to track.