ci: share one build between the image check and the publish - #8
Merged
Conversation
Two workflows built this image and held byte-identical copies of the architecture matrix to do it. That is the same shape as the COPY list in the Dockerfile that shipped a broken image: a hand-kept list, in two places, that nothing forces to agree. Add an architecture to one and forget the other and the check stops shadowing the publish, which is the one property that makes it a check at all. image-build.yml holds the matrix and the buildx invocation now, and has no trigger of its own — `workflow_call` only. Its single input is `push`, which is the entire difference between the two callers: false builds to `type=cacheonly`, running every RUN in the Dockerfile including the stage-2 smoke checks and exporting nothing; true pushes each architecture by digest and records it for the merge job. The permission boundary survives the sharing, and is stronger than before. A called workflow is capped by its caller's grant, so image-check.yml calling with `contents: read` cannot push whatever the shared file asks for. `packages: write` moves off the top of the publish workflow and onto the two jobs that need it, so a job added later does not inherit the ability to publish by default. image.yml becomes image-publish.yml. `image` and `image-check` side by side read as "the image one" and "some extra check", which hid that the real difference is publish versus verify. The Dockerfile's header referenced the old path and now references the new one. Dispatching by hand is `gh workflow run image-publish.yml --ref main`; the old name will not resolve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Answers "why are there three workflows?" — by making the answer defensible.
The problem
image.ymlandimage-check.ymlboth built this image, and each held its own byte-identical copy of the architecture matrix:That is the same shape as the
COPYlist in the Dockerfile that shipped a broken image in #5: a hand-kept list, in two places, that nothing forces to agree. Add an architecture to one and forget the other, and the check stops shadowing the publish — which is the one property that makes it a check.The change
image-build.ymlholds the matrix and the buildx invocation, and has no trigger of its own —workflow_callonly, so it cannot run by itself. Its single input ispush, which is the entire difference between the callers:pushfalsetype=cacheonlyRUNexecutes, including the stage-2 smoke checks; nothing is exportedtruetype=image,push-by-digest=trueimage.yml→image-publish.yml.imageandimage-checkside by side read as "the image one" and "some extra check", which hid that the real difference is publish versus verify.The permission boundary got stronger, not weaker
I originally justified two files on the token — a PR-triggered job must never hold
packages: write— and that was weaker than it sounded, since job-levelpermissions:can express the same thing in one file. Two things now make it structural:image-check.ymlcalls withcontents: read, so the shared build cannot push from a pull request no matter what that file asks for.packages: writemoved off the top of the publish workflow onto the two jobs that need it, so a job added later doesn't silently inherit the ability to publish.Verification
image-build.yml:1, others 0packages: writeappears only inimage-publish.ymlimage-check.ymlare comments)workflows/image.ymlreferencestype=cacheonlyis a real exporterThe
image-checkrun on this PR is the real test — it exercises the reusable workflow on both architectures, which is the changed machinery.Note
Manual dispatch is now
gh workflow run image-publish.yml --ref main. The old name will not resolve. Renaming also starts a fresh run history for the workflow; the oldimageentry keeps its past runs.🤖 Generated with Claude Code