Docker Buildx and Multi-Platform Images
One tag serving amd64, arm64 and arm/v7. Three things block this on a fresh host and all three are shown failing first: the default driver cannot emit multi-platform, RUN on a foreign architecture needs emulation, and the builder container has its own localhost.
Dockerfiles and Builds Guide 23 of 46 Advanced
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2
- BuildKitv0.32.2
- Architectureamd64
- TimeAbout 18 min
- Reviewed21 August 2026
Tested on the versions above. Digests, layer IDs and timings vary per build. Available platforms depend on what emulation is registered on your host. Match the shape of the output, not the exact strings.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Docker Host | 2 Core | 4 GB | 50 GB |
Before you start
- Comfortable with docker build and multi-stage builds - guides 8 and 12 in this path.
- A registry to push to. This guide uses a local registry:3 container - see guide 20.
- Host port 5000 free.
-
See which builder you are using
Every
docker buildruns on a builder. A fresh install has one, nameddefault, using thedockerdriver. Note what it reports: a single platform group. That default builder is the reason the next step fails.bash Example session docker buildx lsNAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMSdefault* docker \_ default \_ default running v0.32.2 linux/amd64 (+3)Expected resultOne builder using the docker driver, marked with an asterisk as current.
Success conditionYou can see the driver column.
dockeris the limited one;docker-containeris the capable one. -
Watch it fail on a foreign architecture
This is worth running rather than reading about, because the error is specific and you will meet it. The amd64 half succeeds. The arm64 half pulls its base image happily - images for other architectures download fine - and then dies the moment it has to execute something. That single failure actually hides two independent requirements, and conflating them is why this topic frustrates people. First, the
dockerdriver cannot output a multi-platform image at all - it has one image store holding one architecture. Second, any RUN on a foreign architecture needs binary emulation registered with the kernel, normally QEMU via binfmt_misc. A Dockerfile with no RUN needs only the first; one with RUN needs both. Installing emulation (docker run --privileged --rm tonistiigi/binfmt --install all) is a privileged, host-wide change and was deliberately NOT run for this guide - the steps below avoid emulation entirely by using a Dockerfile with no RUN.bash Example session docker buildx build --platform linux/amd64,linux/arm64 -t cg-bx:multi .#7 [linux/amd64 2/2] RUN uname -m > /arch.txt#7 DONE 0.2s#8 [linux/arm64 2/2] RUN uname -m > /arch.txt#8 0.106 exec /bin/sh: exec format errorERROR: failed to solve: process "/bin/sh -c uname -m > /arch.txt" did not complete successfully: exit code: 255Expected resultamd64 completes, arm64 fails with
exec format errorat the first RUN.Success conditionYou reproduced the error. It means exactly what it says: the kernel cannot execute an arm64 binary, because no emulation is registered for it.
-
Create a builder that can emit multiple platforms
docker-containerruns BuildKit in its own container, which is what lets it assemble several architectures into one image index.--driver-opt network=hostmatters here and is explained in the step after next.bash Example session docker buildx create --name cg-builder --driver docker-container --driver-opt network=host --bootstrap#1 creating container buildx_buildkit_cg-builder0 0.4s donecg-builderExpected resultA bootstrap block, then the builder name.
Success condition
docker buildx lsnow lists cg-builder with the docker-container driver. It is a real container -docker psshows it. -
The warning everyone hits
Build with the new builder and the platforms you want. It succeeds - and then tells you the result went nowhere. The docker-container driver builds outside your local image store, so unless you say where the result should go, it stays in the build cache. This warning is the single most common confusion with buildx.
bash Example session docker buildx --builder cg-builder build --platform linux/amd64,linux/arm64,linux/arm/v7 -t cg-bx:multi .#12 [linux/arm64 2/2] COPY hello.txt /hello.txt#12 DONE 0.0sWARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --loadExpected resultA successful build for three architectures, followed by the no-output warning.
Success conditionAll three platforms build.
docker imageswill NOT show the result - that is the warning telling you so, not a failure. -
Why the builder cannot see your registry
Adding
--pushto reach a registry on localhost fails on a default builder, and the message explains itself once you know the builder is a container: itslocalhostis its own network namespace, not the host's. Creating the builder withnetwork=host- as this guide did - is what makeslocalhost:5000resolve to the registry you started.bash Example session docker buildx --builder cg-builder build --platform linux/amd64,linux/arm64 -t localhost:5000/cg-bx:multi --push .ERROR: failed to push localhost:5000/cg-bx:multi: dial tcp [::1]:5000: connect: connection refused# the builder container has its own loopback - the registry is on the HOST'sExpected resultA connection refused against ::1, not a permissions or auth error.
Success conditionYou can explain the error. The alternatives are host networking, or addressing the registry by a name the builder container can resolve.
-
Push all three architectures under one tag
With a host-networked builder and a running registry,
--pushsends every architecture and then a manifest that ties them together. A client pulling this tag gets the variant matching its own platform, automatically.bash Example session docker buildx --builder cg-builder build --platform linux/amd64,linux/arm64,linux/arm/v7 -t localhost:5000/cg-bx:multi --push .#13 pushing layers 0.2s done#13 pushing manifest for localhost:5000/cg-bx:multi@sha256:d6758ef0f8b16769db2797cc0ce89efeef0d1d719c36af27acf24ce165a8e6b0#13 DONE 0.9sExpected resultLayers pushed, then a manifest push with a digest.
Success conditionA manifest digest is returned. That digest identifies the whole multi-architecture set, not one image.
-
Inspect what you published
imagetools inspectreads the manifest list back. The media type is an OCI image index - a list of manifests, one per platform. Theunknown/unknownentries are not errors: they are build attestations (provenance and SBOM metadata) that BuildKit attaches by default, each annotated with the manifest it describes.bash Example session docker buildx imagetools inspect localhost:5000/cg-bx:multiMediaType: application/vnd.oci.image.index.v1+jsonDigest: sha256:d6758ef0f8b16769db2797cc0ce89efeef0d1d719c36af27acf24ce165a8e6b0Manifests: Platform: linux/amd64 Platform: linux/arm64 Platform: linux/arm/v7 Platform: unknown/unknown # attestation-manifest, not a broken entryExpected resultAn image index listing the three platforms plus attestation manifests. Output is abridged.
Success conditionThree real platforms appear. If only one does, the build ran on the default builder rather than the container one.
-
Clean up
The builder is a container and holds its own build cache, so removing it reclaims real disk. Remove the registry too, and the anonymous volume it created.
bash # removes the builder and everything cached inside itdocker buildx rm cg-builderdocker rm -f cg-registrydocker rmi moby/buildkit:buildx-stable-1 registry:3# check docker volume ls for the registry's anonymous volumeExpected resultThe builder and container removed.
Success condition
docker buildx lsno longer lists cg-builder anddocker ps -ais clear of cg-registry.
Troubleshooting
exec format error during a multi-platform build
Why: A RUN instruction has to execute a binary for an architecture your kernel cannot run, and no emulation is registered.
Fix:Either register QEMU emulation - a privileged, host-wide change - or restructure so no RUN executes on the foreign architecture, for example by cross-compiling in a native builder stage and only COPYing the artifact into the target-platform stage.
bash FROM --platform=$BUILDPLATFORM golang:1 AS build # builds natively, fastARG TARGETARCHRUN GOARCH=$TARGETARCH go build -o /app . FROM alpine:3.22 # no RUN, so no emulation neededThe build succeeded but docker images does not show it
Why: The docker-container driver writes outside your local image store. Without --push or --load the result exists only in the build cache.
Fix:Add --push for a registry, or --load for the local store. Note --load only works for a single platform, because your image store cannot hold a multi-architecture entry.
bash docker buildx --builder cg-builder build --platform linux/amd64 -t cg-bx:local --load .Multi-platform builds are extremely slow
Why: Emulated execution is often an order of magnitude slower than native. Every RUN on a foreign architecture pays that cost.
Fix:Cross-compile rather than emulate where the toolchain supports it, and use the BUILDPLATFORM and TARGETARCH build arguments to keep compilation native.