Prebuilt Forgejo Actions tooling images, rebuilt weekly
  • Shell 50.6%
  • Dockerfile 49.4%
Find a file
Andre Rosenhammer 17c75140a5
All checks were successful
build tooling images / Resolve tag and image list (push) Successful in 1s
build tooling images / Build amd64 (push) Successful in 3m7s
build tooling images / Build arm64 (push) Successful in 25m50s
build tooling images / Publish multi-architecture manifests (push) Successful in 27s
Fix manifest registry authentication
2026-08-10 13:54:47 +02:00
.forgejo/workflows Fix manifest registry authentication 2026-08-10 13:54:47 +02:00
images Make configure-buildah-storage executable 2026-08-10 11:45:48 +02:00
scripts Name the failing stage in the Vault token exchange 2026-08-10 11:53:47 +02:00
README.md Authenticate to the registry through Vault instead of the run token 2026-08-10 11:51:26 +02:00

ci-images

Prebuilt tooling images for the Forgejo Actions runners in the homelab cluster.

Before this repo existed, nearly every job started from a bare debian or node image and ran apt-get install as its first step — the manifest validation in homelab-gitops installed kubectl, ripgrep and yamllint on every push, and every container build installed Buildah from scratch. That is the same download, unpacked again, on every run.

These images ship that tooling instead. Jobs start working immediately.

Images

Image Base Contains
forgejo.chief-detektor.dev/andre/ci-base debian:trixie-slim kubectl (pinned to the cluster version), yamllint, ripgrep, shellcheck, jq, perl, curl, git
forgejo.chief-detektor.dev/andre/ci-buildah data.forgejo.org/oci/node:22-trixie buildah, crun, netavark, fuse-overlayfs, jq, curl, git, Node (for actions/checkout)

Both are multi-architecture manifest lists (amd64 and arm64), built natively on a runner of each architecture — the workers are arm64 SBCs and the control plane is amd64, and runs-on: docker may land on either.

ci-base deliberately has no Node runtime. The jobs that use it check out with plain git rather than actions/checkout, so a Node runtime would be ~150 MB that nothing executes. Jobs that want actions/checkout should use ci-buildah, or keep their own base image.

Tags

  • :latest — moved by the most recent successful build. This is what the consuming workflows reference.
  • :YYYYMMDD — the build date, for pinning or for rolling back a job to last week's tooling.

:latest is only updated once both architectures have been pushed, so a job container can never be pulled for an architecture that does not exist yet.

When they are built

  • Weekly, Mondays at 04:17 UTC. That is enough to pick up ordinary Debian security updates.
  • Manually, via Actions → build tooling images → Run workflow. Use this when a CVE advisory lands and waiting for Monday is not acceptable. The dispatch form has an images input to rebuild only base or only buildah.
  • On push to main when images/** or the workflow itself changes.

Builds never reuse a layer cache — the entire point of a weekly rebuild is to re-resolve the apt packages, and a cached apt-get install layer would defeat that.

Consuming an image

jobs:
  validate:
    runs-on: docker
    container:
      image: forgejo.chief-detektor.dev/andre/ci-base:latest
    steps:
      - name: Validate
        run: ./scripts/validate.sh

For container builds, ci-buildah ships /usr/local/bin/configure-buildah-storage, which picks overlay + fuse-overlayfs when the job container has /dev/fuse and falls back to vfs otherwise. Call it once before the first buildah command:

      - name: Configure Buildah storage
        run: configure-buildah-storage

Registry access

No long-lived credential is stored in Forgejo Actions. The build exchanges the run's Forgejo OIDC identity token for a short-lived Vault token and reads the shared registry credential from secret/data/container-registry — the same path and the same flow the rosenhammer-dev-relaunch container job uses.

The automatic per-run Actions token is not an option here: it cannot authenticate against the package registry and fails with invalid username/password.

This requires a Vault role named ci-images under the forgejo-actions JWT auth mount, bound to this repository:

$ vault write auth/forgejo-actions/role/ci-images \
    role_type=jwt \
    user_claim=sub \
    bound_audiences=vault \
    bound_claims_type=string \
    bound_claims=repository=andre/ci-images \
    token_policies=container-registry-read \
    token_ttl=10m \
    token_max_ttl=10m

container-registry-read is the existing read-only policy on secret/data/container-registry.

The packages must stay public so that the runner's Docker daemon can pull them as job containers without credentials — it has no registry login.

Bumping kubectl

images/base/Containerfile pins KUBECTL_VERSION so that kubectl kustomize in CI behaves like the one in Argo CD's repo-server. Bump it in the same change that upgrades the cluster.