How does a single image tag serve both amd64 and arm64 hosts, and how does a client end up pulling the right variant?
answer
- tag → index → per-platform manifests
- platform: os / architecture / variant
- buildx --platform … --push builds the index
- imagetools inspect lists platforms
- exec format error = wrong arch pulled
basics
~20 sThe tag points at an OCI image index (manifest list) instead of a single manifest. The index lists one manifest per platform, each annotated with os and architecture. The client fetches the index, matches its own platform, then pulls only that manifest's config and layers.
solid answer
~50 sA tag can resolve to either a **manifest** (one platform) or an **image index** (many). For multi-platform images it resolves to an index: ``` index → [ {manifest A, platform linux/amd64}, {manifest B, platform linux/arm64/v8} ] ``` On pull, the client sends an `Accept` header advertising index media types, gets the index, selects the entry matching its host `os`/`architecture`/`variant`, then fetches that manifest's config and layer blobs. Nothing from the other platforms is downloaded. You build one with `docker buildx build --platform linux/amd64,linux/arm64 --push`, which builds each variant (natively on multiple nodes, or via QEMU emulation) and pushes a single index. `docker buildx imagetools inspect` shows the index; `--platform` on `docker run` or `docker pull` forces a specific variant. Common gotcha: `repo@sha256:…` may pin the *index* digest — still multi-platform — or a single manifest digest, which pins one architecture and will fail to run elsewhere.
code
bash · 5 linesdocker buildx build --platform linux/amd64,linux/arm64 \
-t myrepo/app:1.4.0 --push .
docker buildx imagetools inspect myrepo/app:1.4.0
docker pull --platform linux/arm64 myrepo/app:1.4.0go deeper
Know that one tag can point at an index listing per-platform images and that the client picks the matching one automatically.
Explain the index/manifest structure, the pull-time platform selection, and how buildx --platform … --push produces it.
Cover build strategy (cross-compile vs emulation vs multi-node builders), digest-pinning pitfalls, and diagnosing exec format errors in a mixed-architecture fleet.
Decide fleet-wide whether to standardize on multi-arch images, weigh build cost and CI topology against arm64 price/performance, and set the pinning and attestation policy.
## The problem A container image contains compiled binaries for one CPU architecture. `amd64` binaries do not run on an `arm64` machine. Yet teams want to write `FROM alpine:3.20` or deploy `myrepo/app:1.4.0` and have it work on x86 servers, Apple Silicon laptops and Graviton nodes alike. The OCI image-spec solves this with an extra level of indirection. ## Image index (manifest list) A registry tag is a mutable pointer to *some* manifest-shaped object. That object can be: - an **image manifest** — one config plus one ordered layer list, i.e. exactly one platform; or - an **image index** — `application/vnd.oci.image.index.v1+json` (the Docker equivalent is `application/vnd.docker.distribution.manifest.list.v2+json`), an array of descriptors, each pointing at a manifest and carrying a `platform` object with `os`, `architecture` and optionally `variant` (for example `arm64/v8`), `os.version` (used heavily on Windows) and `os.features`. So `alpine:3.20` is normally an index, and the per-architecture images hang off it. The index itself contains no layers. ## What happens on pull 1. The client resolves the tag: `GET /v2/<name>/manifests/3.20` with an `Accept` header listing the index and manifest media types it understands. 2. The registry returns whichever object the tag points at, along with a `Docker-Content-Digest` header. 3. If it is an index, the client picks the entry whose `platform` matches the host — its own `runtime.GOOS`/`GOARCH`, or an explicit `--platform` override. 4. It fetches that manifest, then the config blob, then the layer blobs it does not already have. Only the selected platform's blobs are transferred. Storage on the registry side holds all of them, but bandwidth on the client side is per-platform. A legacy client that does not advertise index media types may get a fallback single manifest from registries configured to provide one, or an error. ## Building one Classic `docker build` produces a single-platform image. Multi-platform images are built with **BuildKit** through buildx: - `docker buildx build --platform linux/amd64,linux/arm64 -t repo/app:1.4.0 --push .` The two variants can be produced by cross-compilation (fastest and preferred — use the `TARGETPLATFORM`, `TARGETARCH` build args BuildKit injects and let your compiler cross-target), by QEMU user-mode emulation (simple but slow, and prone to subtle failures in native toolchains), or by a builder with real nodes of each architecture. `--push` is important: a local image store historically could not hold a full index, so the index is assembled and published at push time. `docker buildx imagetools create` can also merge already-pushed single-platform manifests into a new index without rebuilding. ## Inspecting and overriding - `docker buildx imagetools inspect repo/app:1.4.0` lists the platforms in the index; `--raw` dumps the JSON. - `docker manifest inspect` shows the same for legacy manifest lists. - `docker pull --platform linux/arm64 …` and `docker run --platform …` force a variant, which is how you deliberately run an emulated image on a mismatched host. ## Pitfalls worth naming in an interview - **Digest pinning ambiguity.** `repo/app@sha256:<index digest>` still resolves per-platform; `repo/app@sha256:<manifest digest>` pins exactly one architecture and will fail on other hosts with an "no matching manifest" or exec-format error. Know which digest you copied. - **Emulation surprises.** Under QEMU, builds are slow and some toolchains, JITs or `uname`-sniffing scripts misbehave. Cross-compile where you can. - **`exec format error`.** The classic symptom of running an amd64 image on arm64 (or vice versa) — usually because a single-platform image was pulled with an explicit platform or because the index only ever had one entry. - **Base image coverage.** Your image can only be multi-platform if every base and every downloaded binary in the Dockerfile exists for that platform. Hardcoded `curl …_amd64.tar.gz` URLs are the usual culprit; parameterize with `TARGETARCH`. - **Attestations.** Modern buildx adds provenance/SBOM attestation manifests into the index with an `unknown/unknown` platform; tools that naively iterate index entries must skip them.
- A colleague pinned an image by digest and it now fails on arm64 nodes with "no matching manifest". What happened?They copied a per-platform manifest digest rather than the index digest. A manifest digest names exactly one architecture's image, so a host of any other architecture finds no matching entry. Re-pin using the digest the registry reports for the tag itself (the index digest), which still resolves per-platform.
- When would you cross-compile rather than build under QEMU emulation?Almost always, for anything compute-heavy. QEMU user-mode emulation runs the whole foreign toolchain interpreted, so builds can be several times slower and some compilers, JITs and native test suites break or hang under it. Cross-compiling with BuildKit's TARGETOS/TARGETARCH build args keeps the build native and only the final artifact foreign; emulation is a reasonable fallback for interpreted stacks or trivial images.
saying these in an interview costs you the question
- Believing one image contains binaries for every architecture at once, rather than one manifest per platform.
- Assuming plain `docker build` can produce a multi-platform image without buildx/BuildKit.
- Treating any `@sha256:` reference as automatically multi-platform.
- Hardcoding architecture-specific download URLs in a Dockerfile and expecting the arm64 build to work.
- Thinking the client downloads the whole index's layers and picks at runtime.