Uh oh!
There was an error while loading. Please reload this page.
images: mirror digest-pinned indexes byte for byte - #391
Conversation
A Dockerfile that pins its base image by digest almost always pins the multi-arch *index* digest -- it is the digest `docker pull` and Docker Hub print. MirrorBaseImage resolved that reference to the platform image and pushed it under the index digest, which the local registry correctly refuses: push to local registry: PUT .../v2/library/python/manifests/sha256:9b4929a7...: unexpected status code 400 Bad Request: digest mismatch: expected sha256:9b4929a7..., got sha256:1e58d36e... The mirror failure is only a WARN, so the build then proceeds and fails later with an unrelated error -- every digest-pinned FROM is unbuildable, and supply-chain-pinned Dockerfiles are exactly the ones that hit it. An index named by digest is now mirrored as the index itself (children first, then the index manifest), so the destination stores content whose digest is exactly the one the caller pinned. Tag references keep the existing platform-image behavior, which is what saves storage; a digest reference that names a plain manifest is also unchanged. The push logic moves into pushMirrored so the property is testable against in-memory registries: the new tests pin a random two-manifest index by digest, mirror it, and prove the pinned digest resolves at the destination.
§8 and §9 went upstream as fix PRs (kernel/hypeman#391, #393), §10 and §11 as issues (#394, #395). Each finding now names the workaround it retires when the upstream fix ships, so the CI hacks have their deletion triggers on record rather than in memory.
chruffins
left a comment
There was a problem hiding this comment.
thanks for putting up this PR! it's just missing some test coverage but looks good otherwise
Uh oh!
There was an error while loading. Please reload this page.
…sted platform Addresses the review note on kernel#391: the existing by-tag test used a single- platform image, so it proved only that the tag path avoids mirroring the whole index — not that it resolves a multi-platform index down to the requested platform. The new test builds a two-platform index (linux/amd64 + linux/arm64), fetches the tag with WithPlatform(linux/arm64), and asserts the mirror arrives as that arm64 image: not the index, and not the amd64 sibling.
mpuig
commented
Aug 23, 2026
Thanks! Added test coverage in |
mpuig
commented
Aug 23, 2026
@chruffins the requested test coverage is in |
Problem
A Dockerfile that pins its base image by digest almost always pins the multi-arch index digest — it's the digest
docker pulland Docker Hub print.MirrorBaseImageresolves that reference to the platform image and pushes it under the index digest, which the local registry correctly refuses:Because the mirror failure is only a WARN, the build proceeds and fails later with an unrelated error (
create builder instance: image is requiredon v0.3.0), so the root cause is invisible in the API response. Net effect: every digest-pinnedFROMis unbuildable — and supply-chain-pinned Dockerfiles are exactly the ones that hit it.Reproduced on v0.3.0 on a GitHub-hosted ubuntu runner with
FROM python:3.13-alpine@sha256:9b4929a7…; journal evidence above.Fix
An index named by digest is mirrored as the index itself (
remote.WriteIndex— children first, then the index manifest), so the destination stores content whose digest is exactly the one the caller pinned.Unchanged behavior:
The push logic moves into
pushMirroredso the property is testable against in-memory registries (pkg/registry, as inimagepush/manager_test.go): the new tests pin a random two-manifest index by digest, mirror it, and prove the pinned digest resolves at the destination — which is the assertion the old code could not pass.Testing
go test ./lib/images/green (new + existing tests)go build ./...greenFound while building barista on hypeman; happy to adjust if you'd rather handle index-pinned refs differently (e.g. refusing them loudly instead of mirroring all platforms).
Note
Medium Risk
Changes how mirrored images are written to the local registry (index vs platform image). Tag-based mirroring is unchanged; digest-pinned indexes now copy all platforms.
Overview
Fixes
MirrorBaseImageso a digest that names a multi-arch index is mirrored as that index (remote.WriteIndex), matching the digest Dockerfiles typically pin. Previously the platform-resolved image was pushed under the index digest, which registries reject as a digest mismatch — so digest-pinnedFROMlines could not build.Tag references still mirror only the requested platform image (storage-saving path). Single-platform digest refs are unchanged.
Push logic is extracted to
pushMirroredand covered with in-memory registry tests for index-by-digest preservation, tag-as-image, and multi-platform tag resolution.Reviewed by Cursor Bugbot for commit eb23dca. Bugbot is set up for automated code reviews on this repo. Configure here.