ci: build this image on Gitea via the in-cluster buildkitd #7

Merged
markus merged 1 commits from claude/ci/build-image-on-gitea into master 2026-08-31 11:06:13 +00:00
Member

Adds build-image-gitea.yml: publishes this image from the self-hosted Gitea runners via the in-cluster rootless buildkitd, the same path zegit's own images already use.

Why. build-image.yml is GitHub-only, so the image the Gitea runners are built from could only be produced by GitHub. When GitHub Actions became unavailable on 2026-08-29 that circularity bit: the fix for a missing tool in this very image — openssl/jq, whose absence blocked v0.0.134 and v0.0.135 — could not be published on the forge the project is moving to. Today that left a merged fix (#6) unable to reach the cluster.

On the header comment this appears to contradict. build-image.yml says the runners cannot build images because rootless buildah needs user namespaces the pod does not have. That is true of buildah in the job, and does not apply here: buildkitd is its own hardened workload in buildkit-system, and the job only speaks to it over the network with buildctl. Nothing is built inside the runner container, so the hardening posture is untouched.

Behaviour. Same contract as build-image.yml: a master push validates that the Dockerfile still builds on both arches and publishes nothing; only an explicit workflow_dispatch names a tag and pushes. Guessing a tag from a branch push would silently mint versions. A dispatch without ZEGIT_PACKAGE_TOKEN fails loudly rather than reporting success having pushed nothing.

The toolchain assertion is kept, and improved. build-image.yml asserts it with docker run, which does not exist on these runners. Here buildkit does it directly: a stage FROM the published image whose RUN fails when a tool is absent. That is strictly better — buildkit runs it natively per platform, so a package that resolves on amd64 but not arm64 cannot reach half the nodes unnoticed. openssl and jq are on the list, and the comment says to extend it whenever a gate grows a shell dependency, since it only ever covers what someone remembered to add.

Verified, not assumed.

  • The assertion Dockerfile was rendered and built against both images: it PASSES on the image built from current master, and FAILS with MISSING: openssl against the deployed ghcr.io/...:12.11.1-ci5. So it detects exactly the drift that caused this week's incident.
  • Tool install follows the runner's known constraints: workspace-local .bin (non-root, uid 1000), registered via $GITHUB_PATH, with the smoke test using the full path because $GITHUB_PATH only affects subsequent steps.
  • The buildkitd endpoint, buildctl version and auth shape are copied from zegit's working image-gitea.yml, not invented.

Assumptions & Decisions

  • Publishes to gitea.langenoja.se/zegit-zoo/zegit-forgejo-runner, not ghcr.io: the agent has no GitHub write, and pushing from in-cluster avoids the Cloudflare 413 that push-image.sh had to work around with an /etc/hosts pin. This means the flux-config deployment must switch registry when you adopt a tag built here. If you would rather keep ghcr.io as the canonical registry, say so and I will drop this in favour of keeping the GitHub path only.
  • build-image.yml is left in place rather than removed — it still works when GitHub Actions is available, and deleting a working publish path during an outage is not the moment to do it.
  • Assumes ZEGIT_PACKAGE_TOKEN is available to this repo (it is an org-level secret for zegit). If it is not, the dispatch fails with an explicit message rather than silently.

Risk / rollback. Additive: a new workflow file, no change to the Dockerfile or to build-image.yml. Rollback is deleting the file. A master push only validates, so merging this cannot publish anything by itself.

Agent-Session: c49e7dd9-2fb0-4501-8f80-264f8ef3a9b7

Adds `build-image-gitea.yml`: publishes this image from the self-hosted Gitea runners via the in-cluster rootless buildkitd, the same path zegit's own images already use. **Why.** `build-image.yml` is GitHub-only, so the image the Gitea runners are *built from* could only be produced by GitHub. When GitHub Actions became unavailable on 2026-08-29 that circularity bit: the fix for a missing tool in this very image — `openssl`/`jq`, whose absence blocked v0.0.134 and v0.0.135 — could not be published on the forge the project is moving to. Today that left a merged fix (#6) unable to reach the cluster. **On the header comment this appears to contradict.** `build-image.yml` says the runners cannot build images because rootless buildah needs user namespaces the pod does not have. That is true of buildah *in the job*, and does not apply here: buildkitd is its own hardened workload in `buildkit-system`, and the job only speaks to it over the network with `buildctl`. Nothing is built inside the runner container, so the hardening posture is untouched. **Behaviour.** Same contract as `build-image.yml`: a master push validates that the Dockerfile still builds on both arches and publishes nothing; only an explicit `workflow_dispatch` names a tag and pushes. Guessing a tag from a branch push would silently mint versions. A dispatch without `ZEGIT_PACKAGE_TOKEN` fails loudly rather than reporting success having pushed nothing. **The toolchain assertion is kept, and improved.** `build-image.yml` asserts it with `docker run`, which does not exist on these runners. Here buildkit does it directly: a stage `FROM` the published image whose `RUN` fails when a tool is absent. That is strictly better — buildkit runs it natively per platform, so a package that resolves on amd64 but not arm64 cannot reach half the nodes unnoticed. `openssl` and `jq` are on the list, and the comment says to extend it whenever a gate grows a shell dependency, since it only ever covers what someone remembered to add. **Verified, not assumed.** - The assertion Dockerfile was rendered and built against both images: it PASSES on the image built from current master, and FAILS with `MISSING: openssl` against the deployed `ghcr.io/...:12.11.1-ci5`. So it detects exactly the drift that caused this week's incident. - Tool install follows the runner's known constraints: workspace-local `.bin` (non-root, uid 1000), registered via `$GITHUB_PATH`, with the smoke test using the **full path** because `$GITHUB_PATH` only affects subsequent steps. - The buildkitd endpoint, buildctl version and auth shape are copied from zegit's working `image-gitea.yml`, not invented. **Assumptions & Decisions** - Publishes to `gitea.langenoja.se/zegit-zoo/zegit-forgejo-runner`, not ghcr.io: the agent has no GitHub write, and pushing from in-cluster avoids the Cloudflare 413 that `push-image.sh` had to work around with an /etc/hosts pin. **This means the flux-config deployment must switch registry** when you adopt a tag built here. If you would rather keep ghcr.io as the canonical registry, say so and I will drop this in favour of keeping the GitHub path only. - `build-image.yml` is left in place rather than removed — it still works when GitHub Actions is available, and deleting a working publish path during an outage is not the moment to do it. - Assumes `ZEGIT_PACKAGE_TOKEN` is available to this repo (it is an org-level secret for zegit). If it is not, the dispatch fails with an explicit message rather than silently. **Risk / rollback.** Additive: a new workflow file, no change to the Dockerfile or to `build-image.yml`. Rollback is deleting the file. A master push only validates, so merging this cannot publish anything by itself. Agent-Session: c49e7dd9-2fb0-4501-8f80-264f8ef3a9b7
agent added 1 commit 2026-08-31 11:04:47 +00:00
build-image.yml is GitHub-only, so the image the Gitea runners are built FROM
could only be produced by GitHub. When GitHub Actions went unavailable on
2026-08-29 that circularity bit: the fix for openssl/jq missing from this image
— the absence that blocked v0.0.134 and v0.0.135 — could not be published on the
forge the project is moving to.

This publishes from the self-hosted runners through the in-cluster rootless
buildkitd, the path zegit images already use. buildah needing user namespaces
does not apply: buildkitd is a separate hardened workload and the job only
speaks to it over the network, so nothing is built inside the runner container.

Same contract as build-image.yml: a master push validates both arches and
publishes nothing, only an explicit dispatch names a tag and pushes, and a
dispatch without a package-write token fails loudly instead of reporting success
having pushed nothing.

The toolchain assertion is kept but done with buildkit rather than docker run,
which these runners lack: a stage FROM the published image whose RUN fails on a
missing tool, built natively for both arches. Verified it passes against an
image built from current master and fails with MISSING: openssl against the
deployed ci5.
agent marked the pull request as ready for review 2026-08-31 11:04:53 +00:00
markus merged commit 86cb6540bb into master 2026-08-31 11:06:13 +00:00
markus deleted branch claude/ci/build-image-on-gitea 2026-08-31 11:06:13 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: zegit-zoo/zegit-forgejo-runner#7