- Dockerfile: optional ARG PKG_CACHE; the apt RUN (dist-upgrade + pinned haproxy) rewrites sources via scripts/pkg-cache-repos.sh apt and restores them before the RUN ends. Without PKG_CACHE: upstream Debian, as before. - coraza-spoa/Dockerfile: same for the build stage's apt RUN; go mod download / go build (build + catalog stages) use go-env (GOPROXY via the cache with proxy.golang.org fallback; GOSUMDB unchanged) and restore. Build context is ./coraza-spoa, so coraza-spoa/scripts/pkg-cache-repos.sh and coraza-spoa/keys/pkg-cache/ are byte-identical copies of the root ones. - build-push.yaml / build-push-coraza.yaml: "Probe package cache" step (TLS with the pinned CA), PKG_CACHE build-arg. Bases come from repo.anhonesthost.net / gcr.io, not docker.io: registry mirror not used. - mirror-base-image.yaml: resolve the tag's digest directly at Docker Hub (.gitea/scripts/resolve-base-image.sh) and pull docker.io/library/<img>@sha256 so the mirrored content is exactly the digest Docker Hub reported; same destination tags. Values reach run: via env only. - Canonical copies (byte-identical) of scripts/pkg-cache-repos.sh, .gitea/scripts/resolve-base-image.sh and keys/pkg-cache/ from cloud-apache-container. - VERSION 2026.10.7 so the rebuild does not overwrite the 2026.10.6 tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
102 lines
4.8 KiB
YAML
102 lines
4.8 KiB
YAML
name: Mirror base images
|
|
run-name: weekly base-image mirror
|
|
|
|
# Pulls each declared base image from upstream and re-pushes to the in-house
|
|
# registry, so any of our images that FROM these don't depend on docker.io's
|
|
# Cloudflare R2 blob storage being reachable. The 2026-05-12 Cloudflare
|
|
# incident motivated this for python:3.12-slim and again for golang:1.25
|
|
# when the coraza-spoa build hit the same blob-fetch failure.
|
|
#
|
|
# Adding a new mirror = add one entry to the matrix below. The destination
|
|
# tag is always cloud-hosting-platform/<image>:<tag>, matching upstream.
|
|
|
|
on:
|
|
schedule:
|
|
# Mondays 06:00 UTC — outside customer peak hours and well before the
|
|
# typical Tuesday/Thursday push cycles. workflow_dispatch lets us trigger
|
|
# manually from the Gitea UI when upstream publishes patches.
|
|
- cron: '0 6 * * 1'
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
Mirror-Base:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
# fail-fast=false so one image's upstream being down doesn't block the
|
|
# others from refreshing.
|
|
fail-fast: false
|
|
matrix:
|
|
# hub: the Docker Hub IMAGE:TAG whose digest is resolved DIRECTLY at
|
|
# registry-1.docker.io (resolve-base-image.sh expects no docker.io/
|
|
# prefix; official images resolve as library/<image>).
|
|
image:
|
|
- { hub: 'python:3.12-slim', dst_path: 'cloud-hosting-platform/python', tag: '3.12-slim' }
|
|
- { hub: 'golang:1.25', dst_path: 'cloud-hosting-platform/golang', tag: '1.25' }
|
|
|
|
steps:
|
|
# Only for .gitea/scripts/resolve-base-image.sh.
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# Supply chain: ask Docker Hub itself (over HTTPS, never via a mirror) which
|
|
# digest the tag points at right now, then pull exactly that digest. What
|
|
# lands in the in-house registry is therefore content-addressed to the
|
|
# digest Docker Hub reported - not whatever a `docker pull TAG` on the
|
|
# runner happened to resolve through any configured registry mirror.
|
|
# If the lookup fails the job FAILS and last week's mirror stays in place.
|
|
- name: Resolve ${{ matrix.image.hub }} digest (Docker Hub, direct)
|
|
id: base
|
|
env:
|
|
HUB_TAG: ${{ matrix.image.hub }}
|
|
run: |
|
|
set -euo pipefail
|
|
ref=$(.gitea/scripts/resolve-base-image.sh "$HUB_TAG")
|
|
[[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]] || { echo "::error::bad digest reference for ${HUB_TAG}"; exit 1; }
|
|
echo "base-image: ${HUB_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
|
echo "ref=${ref}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Login to in-house registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: repo.anhonesthost.net
|
|
username: ${{ secrets.CI_USER }}
|
|
password: ${{ secrets.CI_TOKEN }}
|
|
|
|
- name: Pull by digest, retag, push ${{ matrix.image.hub }}
|
|
# Values reach the script via env, never via ${{ }} inside run:.
|
|
env:
|
|
REF: ${{ steps.base.outputs.ref }}
|
|
DST_PATH: ${{ matrix.image.dst_path }}
|
|
DST_TAG: ${{ matrix.image.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "$REF" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]] || { echo "::error::bad digest reference '${REF}'"; exit 1; }
|
|
[[ "$DST_PATH" =~ ^[a-z0-9][a-z0-9._/-]*$ && "$DST_TAG" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]] || { echo "::error::bad destination"; exit 1; }
|
|
name="${REF%@*}"; digest="${REF#*@}"
|
|
[[ "$name" == */* ]] || name="library/${name}" # official images
|
|
SRC="docker.io/${name}@${digest}"
|
|
DST="repo.anhonesthost.net/${DST_PATH}:${DST_TAG}"
|
|
|
|
echo "::group::Pulling ${SRC}"
|
|
docker pull "${SRC}"
|
|
echo "::endgroup::"
|
|
|
|
# The pulled image must be recorded under exactly the digest Docker
|
|
# Hub reported (docker refuses content that does not match it anyway).
|
|
repo_digests=$(docker image inspect "${SRC}" -f '{{range .RepoDigests}}{{println .}}{{end}}')
|
|
grep -qxF -e "docker.io/${name}@${digest}" -e "${name#library/}@${digest}" -e "${name}@${digest}" <<<"$repo_digests" \
|
|
|| { echo "::error::pulled image is not recorded under ${digest}"; exit 1; }
|
|
echo "upstream digest: ${SRC}"
|
|
|
|
docker tag "${SRC}" "${DST}"
|
|
|
|
echo "::group::Pushing ${DST}"
|
|
docker push "${DST}"
|
|
echo "::endgroup::"
|
|
|
|
# The mirror tag's manifest digest (single-platform image as pushed);
|
|
# differs from the multi-arch index digest above by design.
|
|
dst_digests=$(docker image inspect "${DST}" -f '{{range .RepoDigests}}{{println .}}{{end}}')
|
|
DST_DIGEST=$(grep -m1 -F "repo.anhonesthost.net/${DST_PATH}@" <<<"$dst_digests" || true)
|
|
echo "mirror digest: ${DST_DIGEST:-<unknown>}"
|