Skip to content

Tag builder latest by the digest of the full manifest list - #1450

Merged
pacostas merged 1 commit into
paketo-buildpacks:mainfrom
alekseinovikov:builder-latest-tag-by-digest
Oct 27, 2026
Merged

pacostas merged 1 commit into
paketo-buildpacks:mainfrom
alekseinovikov:builder-latest-tag-by-digest

Conversation

@alekseinovikov

@alekseinovikov alekseinovikov commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes paketo-buildpacks/builder-noble-java-tiny#344.

pack builder create --publish pushes an image per target under the version tag before it pushes the manifest list there (pkg/client/create_builder.go), and the registry can still return one of those right after the push. On builder-noble-java-tiny 0.0.200 (run 39973018505), crane tag <version> latest read :0.0.200 about 2 s after the manifest list was pushed and copied its linux/arm64 child (sha256:771c7c33…) to latest. Every amd64 build on the default Spring Boot / pack builder then failed with exec /cnb/lifecycle/creator: exec format error. 0.0.199 used the same runner image and tool versions and tagged the index correctly, so this is a race, not a tooling change.

Change

In builder/.github/workflows/push-image.yml, read the version tag with crane manifest until it lists every [[targets]] of builder.toml, then tag latest by the digest of exactly that manifest. Since crane manifest prints the raw bytes, sha256sum of its output is the digest, and the tag is never read twice. A builder with at most one target is tagged right away. If the version tag still doesn't list every target after 12 reads, 5 s apart, the job fails, and the existing failure job files an issue.

Alternatives considered:

  • pack builder create --tag latest: pack applies additional tags only to the per-target images, not to the manifest list, so latest would always end up single-platform.
  • --append-image-name-suffix: fixes the race at the source, but adds two -linux-<arch> tags to Docker Hub per release.

Testing

  • actionlint (with shellcheck) passes.
  • I ran the step against a stub crane that returns the real 0.0.200 manifests:
    • two stale arm64 reads, then the index: latest gets the index digest sha256:7c62dd7e…;
    • always arm64: the job fails after 12 reads;
    • a single-target builder.toml: tagged right away.
  • The workflow itself only runs on a release, so I could not run it end to end.

🤖 Generated with Claude Code

@linux-foundation-easycla

linux-foundation-easycla Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: alekseinovikov / name: alekseinovikov (ea92eba)

@alekseinovikov
alekseinovikov force-pushed the builder-latest-tag-by-digest branch from ea92eba to 755bb1d Compare October 27, 2026 11:03
@alekseinovikov
alekseinovikov force-pushed the builder-latest-tag-by-digest branch from 755bb1d to 5209fbe Compare October 27, 2026 11:28
EXPECTED_TARGETS=$(grep -c '^\[\[targets\]\]' "${{ matrix.builders.path }}/builder.toml" || true)
# pack publishes a manifest list only for more than one target, otherwise a plain image
if [ "${EXPECTED_TARGETS}" -le 1 ]; then
EXPECTED_TARGETS=0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one should be 1, as even on single arch, we always have one target, the amd64. WDYT? It works, because the manifest.json will return 0, but is a little bit confusing. WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now TARGETS counts the entries of the manifest list, not platforms. A single-arch builder is published as a plain image without .manifests, so it’s 0 there, and that’s why I set EXPECTED_TARGETS to 0. I can switch both sides to count platforms instead: a plain image counts as 1 (if .manifests then (.manifests | length) else 1 end), and a builder.toml without targets expects 1. Then it reads naturally and still catches the race (a single-arch image gives 1 where 2 are expected). Want me to change it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank your for describing me the code.

Sorry, What do you mean

(a single-arch image gives 1 where 2 are expected)

Also, what do you think, what is your opinion about which one is more readable?

I like the platform idea, as at the end of the day is a docker issue, not buildpacks one.

@pacostas pacostas Oct 27, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The targets also means in which platform the builder will run, so even in case of the target, to me, this one seems to be 1 too

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

During the race, the version tag points for a moment to the image of a single platform, like the arm64 one that ended up in latest. Counting platforms, that image gives 1 while builder.toml expects 2, so the step waits and reads again instead of tagging it. (a single-arch image gives 1 where 2 are expected)

Counting platforms reads better to me too: TARGETS and EXPECTED_TARGETS mean the same thing, so there’s no “0 means single-arch” trick.I’ll switch it to platforms and push the change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can please also rename the variable name to be platforms instead of targets?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please also remove the comments?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@alekseinovikov
alekseinovikov force-pushed the builder-latest-tag-by-digest branch from 5209fbe to 68a0ae8 Compare October 27, 2026 12:12
pack pushes an image per target under the version tag before it pushes
the manifest list there, and the registry can still return one of those
right after the push. On builder-noble-java-tiny 0.0.200, `crane tag
<version> latest` copied the linux/arm64 image to `latest`, so amd64
builds failed with "exec format error"
(paketo-buildpacks/builder-noble-java-tiny#344).

Read the version tag until it lists every target of builder.toml (fail
after a minute), then tag latest by the digest of exactly that manifest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alekseinovikov
alekseinovikov force-pushed the builder-latest-tag-by-digest branch from 68a0ae8 to 20034e0 Compare October 27, 2026 12:43
@pacostas
pacostas merged commit c08b6f9 into paketo-buildpacks:main Oct 27, 2026
4 checks passed
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.

latest tag points to an arm64-only manifest since v0.0.200 (exec format error on amd64)

2 participants