Skip to content

build: fix partial multi-node image pushes - #4058

Open
tomasperezmolina wants to merge 2 commits into
docker:masterfrom
DataDog:fix-partial-push-upstream
Open

build: fix partial multi-node image pushes#4058
tomasperezmolina wants to merge 2 commits into
docker:masterfrom
DataDog:fix-partial-push-upstream

Conversation

@tomasperezmolina

@tomasperezmolina tomasperezmolina commented Sep 4, 2026

Copy link
Copy Markdown

Why

A failed multi-node registry build must not leave the requested tag pointing to a single-platform image. Commit de05d88f, build: let drivers prepare solve options, correctly gave each node an independent copy of its exporter attributes, exposing code that still depended on those attributes being shared. Only one node was switched to a repository-only digest push; another node could publish the final tag before every platform completed.

Reproduction

Assume repro is a two-node builder where the first node handles linux/arm64 and the second handles linux/amd64. Start with a tag that does not exist.

FROM alpine
ARG TARGETARCH
RUN if [ "$TARGETARCH" = arm64 ]; then sleep 30; exit 1; fi
RUN echo "$TARGETARCH" > /platform
docker buildx build \
  --builder repro \
  --platform linux/arm64,linux/amd64 \
  --tag registry.example/repro:test \
  --push .

# The build fails after the amd64 node has finished exporting.
docker buildx imagetools inspect registry.example/repro:test

On an affected version, the tag exists and contains only linux/amd64, even though the requested multi-platform build failed. Buildx never reaches the final manifest-list assembly because the arm64 solve failed.

Fix

Before 0.37, every node shared the same exporter attribute map, so rewriting the first node implicitly rewrote the others. Once those maps were cloned, the target-wide pushNames guard caused later nodes to skip the rewrite.

BuildWithResultHandler loops over dps, one entry per node, and calls prepareMultiDriverExports with that node's SolveOpt on every iteration. Inside the helper, pushPrepared is local and therefore resets for each node, while pushNames remains target-wide and captures the final tag only once. Each node's first pushed image exporter is consequently changed to a repository-only digest push. Buildx publishes the requested tag only after every node succeeds and the manifest list is assembled. Additional image exporters keep their existing behavior.

Notes

The first commit only extracts the existing exporter preparation. The second commit changes its behavior and adds the regression test.

tomasperezmolina and others added 2 commits September 4, 2026 16:16
Move the existing exporter setup out of the node build loop without changing its behavior.

Environment: Datadog workspace

Co-Authored-By: OpenAI GPT-5.6 <noreply@openai.com>
Signed-off-by: Tomas Perez Molina <tomas.perezmolina@datadoghq.com>
Prepare each node's cloned exporter options so no node publishes the requested tag before all platforms complete.

Environment: Datadog workspace

Co-Authored-By: OpenAI GPT-5.6 <noreply@openai.com>
Signed-off-by: Tomas Perez Molina <tomas.perezmolina@datadoghq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant