Skip to content

feat: mirror-image reusable workflow - #23

Merged
cshuttle merged 1 commit into
mainfrom
feat/mirror-image
Jul 28, 2026
Merged

feat: mirror-image reusable workflow#23
cshuttle merged 1 commit into
mainfrom
feat/mirror-image

Conversation

@cshuttle

Copy link
Copy Markdown
Owner

Lifts the image-mirror recipe out of cshuttle/nmon and generalises it. Everything estate-specific is an input — this repo is public, so no hostnames or registry choices are baked in.

Why

Registry throughput varies enormously by peering, and a badly peered one is not merely slow — it fails. Measured from one site against the same 926MB image:

registry throughput
ghcr.io 192 MB/s
docker.io 86 MB/s
quay.io 79 MB/s
registry.k8s.io 20 MB/s
mcr.microsoft.com 2.2 MB/s

That one registry was 391s of a ~640s CI job, and its sibling CDN blew a 30s client timeout under load and failed builds outright. Mirroring took the job to ~80s.

The README tells adopters to measure first — a mirror only helps if the pull is genuinely the bottleneck. Extraction was 8s of that 400s, and raising runner concurrency against a starved path makes it worse.

Three things a naive version gets wrong

  • Stages to disk rather than crane copy. A streamed copy holds the upload open for the whole download; against a slow source the destination cancels it (stream ID 5; CANCEL; received from peer) after minutes of work.
  • Skips when already current, comparing layer digests. The push adds an OCI source label, so the mirror's manifest and config digests never equal upstream's even when identical — a manifest comparison would re-copy every run, i.e. a 7-minute pull weekly for nothing.
  • Pins --platform. crane defaults to all; mirroring an unused architecture doubles the bytes over exactly the leg being avoided.

Implementation note

Layer extraction is sed/grep/tr, not python or jq — the minimal ARC runner image ships neither reliably, as this repo's own selftest already documents for PyYAML.

tr -d before sed is load-bearing: crane pretty-prints, so a line-based sed leaves the config digest in the comparison and silently compares the wrong thing. I caught that by diffing the shell version against a JSON parser rather than trusting it — it would have re-copied on every run while looking correct.

Verification

Against the live registries: current tag → skip path, absent tag → copy path with no stderr noise, and the shell extraction matches a JSON parse exactly. actionlint -shellcheck= clean.

🤖 Generated with Claude Code

Registry throughput varies enormously by peering, and a badly peered one is not
merely slow — it fails. Measured from one site against the same 926MB image:
ghcr.io 192 MB/s, docker.io 86, quay.io 79, registry.k8s.io 20, and
mcr.microsoft.com 2.2. That one registry was 391s of a ~640s CI job, and its
sibling CDN blew a 30s client timeout under load and failed builds outright.
Mirroring took the job to ~80s.

Lifted from cshuttle/nmon, generalised: source, destination, tag, platform and
runner are all inputs, and nothing estate-specific is baked in — this repo is
public.

Three things it does that a naive version gets wrong, each learned the hard way:

  - STAGES TO DISK rather than `crane copy`. A streamed copy holds the upload
    open for the whole download; against a slow source the destination cancels
    it (`stream ID 5; CANCEL; received from peer`) after minutes of work.
  - SKIPS when already current, comparing LAYER digests. The push adds an OCI
    source label, so the mirror's manifest and config digests never equal
    upstream's even when content is identical — a manifest comparison would
    re-copy every run, which for the weekly re-sync means a 7-minute pull for
    nothing.
  - Pins --platform. crane defaults to `all`, and mirroring an unused
    architecture doubles the bytes over exactly the leg being avoided.

Layer extraction is sed/grep/tr, not python or jq: the minimal ARC runner image
ships neither reliably (this repo's own selftest already works around the
missing PyYAML). `tr -d` before `sed` is load-bearing — crane pretty-prints, so
a line-based sed leaves the CONFIG digest in the comparison and silently
compares the wrong thing. Caught by diffing it against a JSON parser rather
than trusting it.

Verified locally against the live registries: current tag takes the skip path,
absent tag takes the copy path with no stderr noise, and the shell extraction
matches a JSON parse exactly. actionlint clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cshuttle
cshuttle merged commit c991531 into main Jul 28, 2026
7 checks passed
@cshuttle
cshuttle deleted the feat/mirror-image branch July 28, 2026 10:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant