skip to content

Multi-Platform Builds

Producing one image that runs on both arm64 servers and amd64 laptops: emulation, native remote builders, or cross-compiling with the platform build args. Every team on mixed hardware asks how one build covers both, and why the result will not load locally.

part ofDockeroverview, primer and where to startread it →
on this pageshow

questions

4

Why does docker buildx --load reject a multi-platform build, and what do you use instead?

level: middleimportance: must knowfreq 58%

answer

  1. Two flags, two different destinations
  2. Ask where the result is being written
  3. The local store keys one image per tag
  4. A registry is built to hold several architectures
  5. imagetools inspect tells you what landed

basics

~20 s

The --load flag copies the build result into the local Docker image store, which holds one architecture per tag, so a two-platform result has nowhere to go. Push it to a registry with --push instead, or load one platform at a time.

solid answer

~40 s

`docker buildx build --platform linux/amd64,linux/arm64` produces one result covering two architectures, and `--load` is shorthand for the docker exporter, which writes into the engine's classic local image store. That store keys one image per tag with a single architecture, so the export is rejected. There is also an earlier failure: the default builder (the one built into the engine) refuses a multi-platform build outright and tells you to switch builders or turn on the containerd image store. In practice you either `--push` to a registry, which is the natural home for a multi-architecture tag, or `--output type=oci,dest=out.tar` for a file, or narrow to one platform (`--platform linux/arm64 --load`) when you just want to run it locally. Confirm what actually landed with `docker buildx imagetools inspect <tag>`.

code

bash · 6 lines
bash
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t registry.example.com/ingest-batch:2.7 \
  --push .

docker buildx imagetools inspect registry.example.com/ingest-batch:2.7

go deeper

for a junior

Remember the shape of the error rather than the internals: --load puts an image on your machine and your machine wants one architecture, so a two-platform build has to go to a registry with --push.

for a middle

Be ready to explain that --load and --push are exporters, that the classic image store records a single Os/Architecture per tag, and that the default engine-embedded builder refuses multi-platform builds before the export stage is even reached.

for a senior

Show how you structure a pipeline around the limitation: single-platform --load for tests, a final multi-platform --push for release, and an imagetools inspect check so a silently single-arch tag never reaches production.

for a principal

Own the platform decision: whether to enable the containerd image store fleet-wide so developers can load multi-platform images locally, and what that changes for tooling, disk usage and the support burden across mixed developer hardware.

## What `--load` and `--push` really are `docker buildx build` runs the build inside a *builder*, and then an **exporter** decides where the finished result goes. The flags most people use are shorthands for exporters: - `--load` is `--output type=docker` — encode the result in the Docker image format and hand it to the local engine's image store, so that `docker images` and `docker run` can see it. - `--push` is `--output type=image,push=true` — write the result to a registry under the tag you passed with `-t`. Nothing about the build itself changes between the two. What changes is the *destination*, and the destinations do not have the same capabilities. ## Two different failures, with two different messages When someone runs `docker buildx build --platform linux/amd64,linux/arm64 -t ingest-batch:2.7 --load .` for the first time, they usually hit one of two errors, and it matters which. **1. The builder refuses.** The default builder is the one embedded in the Docker engine itself. It can only build for the engine's own platform, so a two-platform request fails before any instruction runs, with a message along the lines of *multi-platform build is not supported for the docker driver* and a hint to switch to another builder or enable the containerd image store. The fix is to create a builder that supports it (`docker buildx create --use`) — the builder machinery itself belongs to a separate discussion; what matters here is that the default one is single-platform. **2. The export refuses.** With a multi-platform-capable builder, the build succeeds — you will see both architectures' steps run — and then the *export* fails. The engine's classic image store records exactly one image per tag, with one `Architecture` and one `Os` field. A result that covers two architectures has no representation there, so the docker exporter rejects it. So the mental model is: the build can produce two architectures, the local store can only remember one. ## What to do instead - **`--push`.** A registry is designed to hold one tag that resolves to several architectures, and a client picks the matching one at pull time. This is why nearly every real multi-platform pipeline is `buildx build --platform ... --push`, never `--load`. - **`--output type=oci,dest=image.tar`.** An OCI layout tarball can hold both architectures. It is useful for air-gapped transfer or for handing the artefact to another tool, but you cannot then `docker load` that multi-platform tarball into the classic store — the same limitation applies. - **Narrow to one platform.** `docker buildx build --platform linux/arm64 -t ingest-batch:2.7 --load .` works fine. This is the everyday local-development case, and it is worth stressing that `--platform` with a *single* value plus `--load` is perfectly legal; only two or more values break. - **Turn on the containerd image store.** Newer Docker Engine and Docker Desktop can back the local image store with containerd, which *can* hold a multi-platform image locally; there, `--load` of a multi-platform build works. That is an engine-level setting, not a build flag, and you cannot assume a colleague's machine has it on. - **Run a local registry.** Pushing to a registry running on localhost and pulling back is a common CI trick when you want a multi-platform artefact without touching a shared registry. ## Verifying what you produced After a `--push`, do not assume. `docker buildx imagetools inspect registry.example.com/ingest-batch:2.7` prints the platforms the tag actually offers; if you only see `linux/amd64`, the `--platform` flag never took effect (a very common mistake is putting it after the context argument, or reusing a shell alias that drops it). `docker image inspect` is the wrong tool for this — it only ever describes the single local image, so it will happily report `arm64` and tell you nothing about what is in the registry. ## Why this shapes CI pipelines Because `--load` is single-platform, most pipelines split the work: build and `--load` one platform to run unit or smoke tests on the runner, then a final `--push` with both platforms for release. The alternative — push first, then pull the image on a runner of each architecture and test it there — costs a registry round trip but tests each architecture natively, which is the only way to catch behaviour that emulation hides.

  • After a --push, how do you confirm the tag really carries both architectures?
    Run `docker buildx imagetools inspect <tag>`; it prints a platform line for each architecture the tag resolves to. `docker image inspect` cannot answer this — it describes only the one image sitting in your local store, so it will report whichever architecture you happen to have pulled.
  • Your CI runner is amd64 but must smoke-test the arm64 image it just built. What are your options?
    Either build and `--load` only `linux/arm64` and run it under emulation on that runner, which requires binfmt handlers registered on the host and is slow and not fully representative, or `--push` the multi-platform tag and pull it on a native arm64 runner. The second costs a registry round trip but is the only way to test the architecture honestly.
  • Does `docker save` give you a way around the --load limitation?
    No. `docker save` exports what is already in the local image store, so it inherits the same single-architecture constraint. If you need a file that holds both architectures, export it straight from the build with `--output type=oci,dest=out.tar`.

A registry is a shelf that can hold several editions of the same book under one title; the classic local image store is a single slot that fits exactly one edition.

saying these in an interview costs you the question

  • Thinking --push and --load are interchangeable destinations
  • Assuming the default builder can build multi-platform
  • Believing the classic local store keeps one image per architecture
  • Claiming docker save can export a multi-platform result
  • Concluding multi-platform needs a separate tag per architecture
  • Verifying a pushed multi-arch tag with docker image inspect

context

open as a page

In a Dockerfile, what do BUILDPLATFORM and TARGETARCH mean, and what does FROM --platform=$BUILDPLATFORM do?

level: middleimportance: should knowfreq 44%

basics

~20 s

BUILDPLATFORM describes the machine running the build; TARGETARCH describes the architecture currently being produced. Pinning a stage with FROM --platform=$BUILDPLATFORM keeps that stage on the builder's own architecture so its toolchain runs natively and cross-produces output for TARGETARCH.

open as a page

A Docker container exits immediately with `exec format error` on an arm64 host but runs on amd64. How do you diagnose and fix it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The kernel could not execute the binary because it was compiled for another architecture and no emulation handler is registered. Check the image's recorded architecture, rebuild the image for the host's platform, or run it with docker run --platform and accept the emulation cost.

open as a page

Your Docker images must now run on both arm64 and amd64 hosts. How do you decide which ones ship both, and keep the two from diverging?

level: principalimportance: nice to knowfreq 27%

basics

~20 s

Decide per image from where it actually runs, not by default: dual-architecture for shared base images and anything developers run locally, single-architecture where a workload is pinned to one host pool. Then test each architecture on native hardware, because build success is not behavioural parity.

open as a page