fix: compose the manifest with imagetools, not docker manifest

A per-arch push came back as "is a manifest list". Since Buildx 0.10,
provenance and SBOM attestations are ON by default, so even a SINGLE-platform
build publishes an OCI index (image + attestation) rather than a plain
manifest — and `docker manifest create` refuses to nest an index inside an
index.

Two changes, either of which alone would fix it, kept together because they
fail in different directions:

  --provenance=false --sbom=false   keeps each per-arch push a plain manifest.
                                    Probed for support first, so an older
                                    docker that lacks the flags still works.
  imagetools create                 composes correctly whether the sources are
                                    plain manifests OR indexes, so attestations
                                    reappearing later cannot break it again.
                                    Falls back to docker manifest when buildx
                                    is unavailable, with the actionable hint in
                                    the failure message.

Verification now accepts either inspector's output and asserts both platforms
are present by name.
This commit is contained in:
agent committed 2026-08-20 14:28:18 +02:00
1 parent b2d4e02c6f
commit 71ba76943a
2 files changed
+36 -6

No files matched your search

+7
View File
@@ -61,6 +61,13 @@ can clear it directly:
A pin you added yourself (no marker) is never touched, and the script refuses to A pin you added yourself (no marker) is never touched, and the script refuses to
run if an unmarked pin points somewhere other than the gateway. run if an unmarked pin points somewhere other than the gateway.
If a per-arch push reports *"is a manifest list"*, that is BuildKit's provenance
attestation: since Buildx 0.10 it is on by default, so even a single-platform
build publishes an OCI **index** rather than a plain manifest, and
`docker manifest create` refuses to nest one. The script builds with
`--provenance=false --sbom=false` and composes with `docker buildx imagetools
create`, which handles either shape.
Requirements: a Gitea PAT with **`write:package`** (`read:package` is pull-only), Requirements: a Gitea PAT with **`write:package`** (`read:package` is pull-only),
LAN reachability of the gateway, and qemu/binfmt registered for the arm64 build. LAN reachability of the gateway, and qemu/binfmt registered for the arm64 build.
+29 -6
View File
@@ -91,23 +91,46 @@ esac
docker login "$REGISTRY" || die "login failed — the PAT needs write:package, not read:package" docker login "$REGISTRY" || die "login failed — the PAT needs write:package, not read:package"
# --provenance=false --sbom=false: since Buildx 0.10 attestations are ON by
# default, so even a SINGLE-platform build publishes an OCI INDEX (image +
# attestation) instead of a plain manifest. `docker manifest create` then
# refuses it with "is a manifest list". Suppressing them keeps each arch a
# plain manifest. Harmless if the flags are unsupported — we probe first.
PROV_FLAGS=""
if docker build --help 2>/dev/null | grep -q -- '--provenance'; then
PROV_FLAGS="--provenance=false --sbom=false"
fi
for arch in amd64 arm64; do for arch in amd64 arm64; do
echo "building linux/$arch" echo "building linux/$arch"
docker build --platform "linux/$arch" -t "$REPO:$TAG-$arch" . \ # shellcheck disable=SC2086 # PROV_FLAGS is deliberately word-split
|| die "build failed for $arch (arm64 needs qemu/binfmt registered)" docker build $PROV_FLAGS --platform "linux/$arch" -t "$REPO:$TAG-$arch" . \
|| die "build failed for $arch (arm64 needs qemu/binfmt: docker run --privileged --rm tonistiigi/binfmt --install arm64)"
# Explicit tag, never $_ — in zsh $_ is the last ARGUMENT, which here is '.' # Explicit tag, never $_ — in zsh $_ is the last ARGUMENT, which here is '.'
docker push "$REPO:$TAG-$arch" || die "push failed for $arch" docker push "$REPO:$TAG-$arch" || die "push failed for $arch"
done done
# `docker buildx imagetools create` rather than `docker manifest create`: it
# composes correctly whether the sources are plain manifests OR indexes, so it
# does not fail with "is a manifest list" when attestations sneak back in. It
# is a client-side registry operation, so it honours the /etc/hosts pin too.
if docker buildx imagetools create --help >/dev/null 2>&1; then
docker buildx imagetools create -t "$REPO:$TAG" "$REPO:$TAG-amd64" "$REPO:$TAG-arm64" \
|| die "imagetools create failed"
else
docker manifest rm "$REPO:$TAG" >/dev/null 2>&1 || true docker manifest rm "$REPO:$TAG" >/dev/null 2>&1 || true
docker manifest create "$REPO:$TAG" --amend "$REPO:$TAG-amd64" --amend "$REPO:$TAG-arm64" docker manifest create "$REPO:$TAG" --amend "$REPO:$TAG-amd64" --amend "$REPO:$TAG-arm64" \
|| die "manifest create failed — if it says \"is a manifest list\", rebuild with --provenance=false"
docker manifest push "$REPO:$TAG" docker manifest push "$REPO:$TAG"
fi
# Verify by reading it back, rather than trusting the push's exit code. # Verify by reading it back, rather than trusting the push's exit code.
echo "verifying the published manifest lists both architectures" echo "verifying the published manifest lists both architectures"
got=$(docker manifest inspect "$REPO:$TAG" | grep -o '"architecture": *"[a-z0-9]*"' | sort -u | wc -l) inspect=$(docker buildx imagetools inspect "$REPO:$TAG" 2>/dev/null || docker manifest inspect "$REPO:$TAG")
[ "$got" -ge 2 ] || die "published manifest does not list two architectures" for want in amd64 arm64; do
docker manifest inspect "$REPO:$TAG" | grep -o '"architecture": *"[a-z0-9]*"' | sort -u echo "$inspect" | grep -q "$want" || die "published manifest is missing linux/$want"
done
echo "$inspect" | grep -iE 'platform|architecture' | sort -u | head -6
cat <<EOF cat <<EOF