Files
zegit-forgejo-runner/README.md
T
agent 71ba76943a fix: compose the manifest with imagetools, not docker manifest
A per-arch push came back as "is a manifest list". Since Buildx 0.10,
provenance and SBOM attestations are ON by default, so even a SINGLE-platform
build publishes an OCI index (image + attestation) rather than a plain
manifest — and `docker manifest create` refuses to nest an index inside an
index.

Two changes, either of which alone would fix it, kept together because they
fail in different directions:

  --provenance=false --sbom=false   keeps each per-arch push a plain manifest.
                                    Probed for support first, so an older
                                    docker that lacks the flags still works.
  imagetools create                 composes correctly whether the sources are
                                    plain manifests OR indexes, so attestations
                                    reappearing later cannot break it again.
                                    Falls back to docker manifest when buildx
                                    is unavailable, with the actionable hint in
                                    the failure message.

Verification now accepts either inspector's output and asserts both platforms
are present by name.
2026-08-20 14:28:18 +02:00

83 lines
3.6 KiB
Markdown

# zegit-forgejo-runner
Custom toolchain image for the cluster's self-hosted **Gitea Actions (Forgejo)
runner**, which runs in *host mode* (Docker disabled) — workflow steps execute
inside this image, so it must carry node/git/make/etc. that the stock
`code.forgejo.org/forgejo/runner` image lacks.
This repo lives only on Gitea (`zegit-zoo/zegit-forgejo-runner`). The cluster-side
deployment and rationale live in `flux-config` (`runners.md`,
`applications/forgejo-runner/`).
## Build & push
The runner needs this image to run *any* job, so the **first** build must be done
by hand (CI can't bootstrap itself). The cluster is mixed amd64/arm64, so build
multi-arch:
```sh
docker buildx create --use --name zegit-ci 2>/dev/null || true
docker login gitea.langenoja.se # username + a Gitea personal access token
docker buildx build --platform linux/amd64,linux/arm64 \
-t gitea.langenoja.se/zegit-zoo/zegit-forgejo-runner:12.11.1-ci --push .
```
The Gitea package is **public**, so the `forgejo-runner-system` namespace pulls it
without an imagePullSecret.
## Wire it into the cluster
After the push succeeds, in `flux-config`
`applications/forgejo-runner/base/deployment-forgejo-runner.yaml` set the `image:`
on **both** the `register` initContainer and the `forgejo-runner` daemon to
`gitea.langenoja.se/zegit-zoo/zegit-forgejo-runner:12.11.1-ci`, then commit (Flux
reconciles). Do this only *after* the image exists, or the pod ImagePullBackOffs.
## Tag scheme
`<base-runner-version>-ci` (e.g. `12.11.1-ci`). Must satisfy the cluster's
Gatekeeper `enforce-semantic-image-tags` constraint — never `:latest`.
## Publishing a new image
./push-image.sh 12.11.1-ci4
The registry hostname in the image reference must stay `gitea.langenoja.se`,
because that is what the cluster pulls. The script therefore keeps the NAME and
changes only the ROUTE: it pins that hostname to the Istio gateway on the LAN
(`10.40.0.10`) for the duration of the push, then removes the pin.
That matters because `gitea.langenoja.se` resolves to Cloudflare, which caps
request bodies below a registry layer and returns **413 Payload Too Large**. The
gateway serves a `*.langenoja.se` wildcard cert, so TLS still validates normally
— no `insecure-registries`, no `--tls-verify=false`.
If a run is killed before it can clean up, the pin is left behind. It carries a
marker comment, so the next run recognises it as its own and removes it — or you
can clear it directly:
./push-image.sh --unpin
A pin you added yourself (no marker) is never touched, and the script refuses to
run if an unmarked pin points somewhere other than the gateway.
If a per-arch push reports *"is a manifest list"*, that is BuildKit's provenance
attestation: since Buildx 0.10 it is on by default, so even a single-platform
build publishes an OCI **index** rather than a plain manifest, and
`docker manifest create` refuses to nest one. The script builds with
`--provenance=false --sbom=false` and composes with `docker buildx imagetools
create`, which handles either shape.
Requirements: a Gitea PAT with **`write:package`** (`read:package` is pull-only),
LAN reachability of the gateway, and qemu/binfmt registered for the arm64 build.
Multi-arch is not optional. `forgejo-runner` runs on arm64 nodes and
`zegit-runner` on amd64, neither deployment sets a `nodeSelector`, and both use
this single tag.
Two traps the script exists to prevent: pushing to the bare MagicDNS name
(`gitea-system-gitea-tailscale-http`) makes Docker treat it as a **Docker Hub
namespace**, since a registry host is only recognised when it contains a dot or
colon; and `docker push $_` pushes `.`, because `$_` is the last ARGUMENT of the
build command, not its tag.