Contents
Build Multi-Platform Images (amd64 and arm64): Docker Buildx Tutorial
The problem: exec format error
You build an image on an Apple Silicon (M-series) Mac, push it to an ordinary cloud server, and the container exits as soon as it starts:
exec /app: exec format error
The reason is a different CPU architecture. An Apple Silicon Mac builds arm64 images by default, while most cloud servers are amd64 (x86_64). A program is compiled to machine code for one kind of CPU, another kind of CPU can't run it, and you get this error. We reproduce it later in this chapter.
Common platforms
| Platform | Typical devices |
|---|---|
linux/amd64 | Most cloud servers, Intel / AMD computers |
linux/arm64 | Apple Silicon Macs, AWS Graviton, Google Axion, Raspberry Pi 4/5 (64-bit OS) |
linux/arm/v7 | Older Raspberry Pi models (32-bit OS) |
What is a multi-platform image
Official images (such as nginx and node) are multi-platform images: one name and tag holds versions for several architectures. Check which platforms nginx:1.30-alpine supports:
$ docker buildx imagetools inspect nginx:1.30-alpine | grep "Platform:" | grep -v unknown
Platform: linux/amd64
Platform: linux/arm/v6
Platform: linux/arm/v7
Platform: linux/arm64/v8
Platform: linux/386
Platform: linux/ppc64le
Platform: linux/riscv64
Platform: linux/s390x
The same nginx:1.30-alpine covers 8 architectures. On docker pull, Docker picks the version that matches the current host, so the same docker run nginx:1.30-alpine works on a Mac and on a cloud server. You can make your own images work the same way.
Example: a Go web service
The demo is a Go program that reports the architecture it runs on. The goapp folder:
goapp/
├── go.mod
├── main.go
└── Dockerfile
go.mod:
module example.com/server
go 1.27
main.go:
package main
import (
"fmt"
"net/http"
"runtime"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from %s/%s\n", runtime.GOOS, runtime.GOARCH)
})
fmt.Printf("Listening on :8080 (%s/%s)\n", runtime.GOOS, runtime.GOARCH)
http.ListenAndServe(":8080", nil)
}
Dockerfile:
FROM --platform=$BUILDPLATFORM golang:1.27 AS build
ARG TARGETOS TARGETARCH
WORKDIR /src
COPY . .
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o /out/app .
FROM gcr.io/distroless/static-debian13
COPY --from=build /out/app /app
ENTRYPOINT ["/app"]
This Dockerfile cross-compiles. The key is a few arguments that BuildKit provides automatically:
| Argument | Meaning | Value in this example |
|---|---|---|
BUILDPLATFORM | Platform of the machine running the build | linux/amd64 |
TARGETPLATFORM | Platform of the image being produced | linux/amd64 or linux/arm64 |
TARGETOS / TARGETARCH | The target platform split into OS and architecture | linux / amd64 or arm64 |
FROM --platform=$BUILDPLATFORM golang:1.27: the compile stage always runs on the build machine's own architecture, at native speed.GOOS=$TARGETOS GOARCH=$TARGETARCH: the Go compiler outputs a binary for the target architecture directly.- The last stage is only a
COPYand runs no programs, so there is no need to emulate the target CPU.
Build both platforms at once with buildx
docker buildx is the build command that uses BuildKit. It is built into Docker Desktop and into docker-buildx-plugin on Linux. First look at the current builders:
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default* docker
\_ default \_ default running v0.34.0 linux/amd64 (+3)
Docker 29 uses the containerd image store by default, so the default docker builder can build multi-platform images and keep them locally. List the platforms with --platform:
$ docker buildx build --platform linux/amd64,linux/arm64 -t goapp:1.0 .
[+] Building 106.9s (16/16) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 304B 0.0s
=> [linux/arm64 internal] load metadata for gcr.io/distroless/static-debian13:latest 2.0s
=> [linux/amd64 internal] load metadata for gcr.io/distroless/static-debian13:latest 1.8s
=> [linux/amd64 internal] load metadata for docker.io/library/golang:1.27 1.5s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [linux/amd64 build 1/4] FROM docker.io/library/golang:1.27@sha256:e432b43af23a9328d56a7c499be0476810aa344acbcf65fc7c455d4ff5a40602 19.3s
=> => resolve docker.io/library/golang:1.27@sha256:e432b43af23a9328d56a7c499be0476810aa344acbcf65fc7c455d4ff5a40602 0.0s
=> => sha256:a6132307e9f8de84712b13dc48449cc6a8a80bc26e35b5c9dd04a6df2b23e85e 102.41MB / 102.41MB 3.3s
=> => sha256:b2b6413ae8f393d11d32262c8dc6bd053dbaea3d074f95edd5605d3eb116e3c2 71.15MB / 71.15MB 2.5s
=> => sha256:801401a9a8ba22fe48ef647d8eed0d6d8adc41baabf2fae9057ff38b2622f35a 127B / 127B 0.3s
=> => sha256:4dc3950125ebc9ed195e1101fc6c02e2fce113ba0f53bfb05b2cdf58747df9af 67.81MB / 67.81MB 2.6s
=> => sha256:62fe2e747f69f2f414c7e9eb1553155cf0a658398f0462557f458821dce2b29d 25.64MB / 25.64MB 1.5s
=> => sha256:4846f29007305297af653c57bf9b60ba8f676a38695c68d98460063442581671 49.39MB / 49.39MB 1.4s
=> => extracting sha256:4846f29007305297af653c57bf9b60ba8f676a38695c68d98460063442581671 2.7s
=> => extracting sha256:62fe2e747f69f2f414c7e9eb1553155cf0a658398f0462557f458821dce2b29d 0.9s
=> => extracting sha256:4dc3950125ebc9ed195e1101fc6c02e2fce113ba0f53bfb05b2cdf58747df9af 3.1s
=> => extracting sha256:a6132307e9f8de84712b13dc48449cc6a8a80bc26e35b5c9dd04a6df2b23e85e 4.3s
=> => extracting sha256:b2b6413ae8f393d11d32262c8dc6bd053dbaea3d074f95edd5605d3eb116e3c2 4.9s
=> => extracting sha256:801401a9a8ba22fe48ef647d8eed0d6d8adc41baabf2fae9057ff38b2622f35a 0.0s
=> => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 0.0s
=> [linux/amd64 stage-1 1/2] FROM gcr.io/distroless/static-debian13:latest@sha256:58133991db06659feaabe0f4e97a35cebf15ef4ea08f8a4c6d2ee5f75e4a 4.3s
=> => resolve gcr.io/distroless/static-debian13:latest@sha256:58133991db06659feaabe0f4e97a35cebf15ef4ea08f8a4c6d2ee5f75e4aa6a0 0.0s
=> => sha256:c4bc6f35ff5e25aa1afe7a148a0b6f10a760f54e298f86a4a4d44b91f1afbfab 143.34kB / 143.34kB 1.0s
=> => sha256:2cc7ee286bf3a9e6af5f71756d7fc8e22e23ce65fec9047d774511ffcef79fa8 124.53kB / 124.53kB 0.9s
=> => extracting sha256:2cc7ee286bf3a9e6af5f71756d7fc8e22e23ce65fec9047d774511ffcef79fa8 0.1s
=> => extracting sha256:c172f21841dff4c8cf45cde46589c1c2616cefe7e819965e92e6d3475c428aa0 0.1s
=> => extracting sha256:218cf840d0d95a86231eb9d33d26ac8a520a4299c6096cb70a0c2fb2a87c9e0a 0.3s
=> => extracting sha256:f6069939f718601b38d25ec5ed5631ed047e4da24acec2e252f8f29d251be718 0.2s
=> => extracting sha256:d6b1b89eccacc15c2420b2776d72c1dae334a00805ed9af54bf2f71e4d536f28 0.0s
=> => extracting sha256:2780920e5dbfbe103d03a583ed75345306e572ec5a48cb10361f046767d9f29a 0.0s
=> => extracting sha256:7c12895b777bcaa8ccae0605b4de635b68fc32d60fa08f421dc3818bf55ee212 0.0s
=> => extracting sha256:3214acf345c0cc6bbdb56b698a41ccdefc624a09d6beb0d38b5de0b2303ecaf4 0.0s
=> => extracting sha256:52630fc75a18675c530ed9eba5f55eca09b03e91bd5bc15307918bbc1a7e7296 0.0s
=> => extracting sha256:dd64bf2dd177757451a98fcdc999a339c35dee5d9872d8f4dc69c8f3c4dd0112 0.0s
=> => extracting sha256:b839dfae01f66e15c6a8b63520557ed315bdfe036342fa7a0c537259f10d7a9a 0.0s
=> => extracting sha256:ebddc55facdc6b1f7e0f30816a5fc7cc62f38abdf76c0a8b0a0ce52085754795 0.1s
=> => extracting sha256:c4bc6f35ff5e25aa1afe7a148a0b6f10a760f54e298f86a4a4d44b91f1afbfab 0.1s
=> [linux/arm64 stage-1 1/2] FROM gcr.io/distroless/static-debian13:latest@sha256:58133991db06659feaabe0f4e97a35cebf15ef4ea08f8a4c6d2ee5f75e4a 4.4s
=> => resolve gcr.io/distroless/static-debian13:latest@sha256:58133991db06659feaabe0f4e97a35cebf15ef4ea08f8a4c6d2ee5f75e4aa6a0 0.0s
=> => sha256:b839dfae01f66e15c6a8b63520557ed315bdfe036342fa7a0c537259f10d7a9a 351B / 351B 0.3s
=> => sha256:ebddc55facdc6b1f7e0f30816a5fc7cc62f38abdf76c0a8b0a0ce52085754795 311B / 311B 0.3s
=> => sha256:6ed1ef0cb1159b9c93a24ceb88b987c6b3a8adff7160940c7c5e6bfc001f1766 143.34kB / 143.34kB 1.7s
=> => sha256:dd64bf2dd177757451a98fcdc999a339c35dee5d9872d8f4dc69c8f3c4dd0112 80B / 80B 0.8s
=> => sha256:52630fc75a18675c530ed9eba5f55eca09b03e91bd5bc15307918bbc1a7e7296 162B / 162B 0.5s
=> => sha256:3214acf345c0cc6bbdb56b698a41ccdefc624a09d6beb0d38b5de0b2303ecaf4 123B / 123B 0.3s
=> => sha256:7c12895b777bcaa8ccae0605b4de635b68fc32d60fa08f421dc3818bf55ee212 188B / 188B 0.3s
=> => sha256:2780920e5dbfbe103d03a583ed75345306e572ec5a48cb10361f046767d9f29a 67B / 67B 0.4s
=> => sha256:d6b1b89eccacc15c2420b2776d72c1dae334a00805ed9af54bf2f71e4d536f28 32.09kB / 32.09kB 0.7s
=> => sha256:f6069939f718601b38d25ec5ed5631ed047e4da24acec2e252f8f29d251be718 254.67kB / 254.67kB 1.1s
=> => sha256:218cf840d0d95a86231eb9d33d26ac8a520a4299c6096cb70a0c2fb2a87c9e0a 288.06kB / 288.06kB 1.8s
=> => sha256:c172f21841dff4c8cf45cde46589c1c2616cefe7e819965e92e6d3475c428aa0 12.68kB / 12.68kB 0.3s
=> => sha256:401d177fd1e8a1e0d7a48139788d026e9a2e919ccb122bedd110ac7cf351f53e 124.50kB / 124.50kB 1.1s
=> => extracting sha256:401d177fd1e8a1e0d7a48139788d026e9a2e919ccb122bedd110ac7cf351f53e 0.1s
=> => extracting sha256:c172f21841dff4c8cf45cde46589c1c2616cefe7e819965e92e6d3475c428aa0 0.0s
=> => extracting sha256:218cf840d0d95a86231eb9d33d26ac8a520a4299c6096cb70a0c2fb2a87c9e0a 0.2s
=> => extracting sha256:f6069939f718601b38d25ec5ed5631ed047e4da24acec2e252f8f29d251be718 0.3s
=> => extracting sha256:d6b1b89eccacc15c2420b2776d72c1dae334a00805ed9af54bf2f71e4d536f28 0.0s
=> => extracting sha256:2780920e5dbfbe103d03a583ed75345306e572ec5a48cb10361f046767d9f29a 0.0s
=> => extracting sha256:7c12895b777bcaa8ccae0605b4de635b68fc32d60fa08f421dc3818bf55ee212 0.0s
=> => extracting sha256:3214acf345c0cc6bbdb56b698a41ccdefc624a09d6beb0d38b5de0b2303ecaf4 0.0s
=> => extracting sha256:52630fc75a18675c530ed9eba5f55eca09b03e91bd5bc15307918bbc1a7e7296 0.0s
=> => extracting sha256:dd64bf2dd177757451a98fcdc999a339c35dee5d9872d8f4dc69c8f3c4dd0112 0.0s
=> => extracting sha256:b839dfae01f66e15c6a8b63520557ed315bdfe036342fa7a0c537259f10d7a9a 0.0s
=> => extracting sha256:ebddc55facdc6b1f7e0f30816a5fc7cc62f38abdf76c0a8b0a0ce52085754795 0.0s
=> => extracting sha256:6ed1ef0cb1159b9c93a24ceb88b987c6b3a8adff7160940c7c5e6bfc001f1766 0.1s
=> [internal] load build context 0.0s
=> => transferring context: 735B 0.0s
=> [linux/amd64 build 2/4] WORKDIR /src 0.1s
=> [linux/amd64 build 3/4] COPY . . 0.0s
=> [linux/amd64 build 4/4] RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o /out/app . 83.3s
=> [linux/amd64->arm64 build 4/4] RUN GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o /out/app . 83.8s
=> [linux/amd64 stage-1 2/2] COPY --from=build /out/app /app 0.0s
=> [linux/arm64 stage-1 2/2] COPY --from=build /out/app /app 0.1s
=> exporting to image 1.4s
=> => exporting layers 0.9s
=> => exporting manifest sha256:bae3f4b45be6e6c37bbe5430c1fd68cf98974b82dd312b81fcf568abb321584c 0.0s
=> => exporting config sha256:795e98f14dcf24dbcc9a133515ee54505a800a4ebf3f2408d89441c3a166c468 0.0s
=> => exporting attestation manifest sha256:379134f8a7c50a77e0bf19cbd9593a884daeac7eb2b7c852ccfa8f81ce7bdd34 0.0s
=> => exporting manifest sha256:d6cd4f43dd3d332129c7b6b9f6720c6da06e070125860d2ab0661e15d251133a 0.0s
=> => exporting config sha256:d61c8d18abcb388876ad82cb6009d9bd18754e6e277ac6822c697d407243594a 0.0s
=> => exporting attestation manifest sha256:2f4085c9f5da45df6153b09141bf7db573806a93a2ed81c64c2ec75e49bfa8e6 0.0s
=> => exporting manifest list sha256:dc8d3f71a9f7adb0de32dd59651adc81dab5b78612cdc7eccc39a5f73608d37e 0.0s
=> => naming to docker.io/library/goapp:1.0 0.0s
=> => unpacking to docker.io/library/goapp:1.0 0.4s
Look at the build output:
- Each step is labeled with its platform, for example
[linux/amd64 build 4/4]. - The arm64 compile step shows as
[linux/amd64->arm64 build 4/4]: compiling for arm64 on an amd64 machine. That is cross-compilation. - The
go buildsteps for the two platforms run in parallel.
Check the result with docker images --tree (only newer Docker versions have the --tree option):
$ docker images --tree goapp:1.0
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
goapp:1.0 dc8d3f71a9f7 25.5MB 11.1MB
├─ linux/amd64 bae3f4b45be6 20.2MB 5.77MB
└─ linux/arm64 d6cd4f43dd3d 5.36MB 5.36MB
goapp:1.0 contains two versions, linux/amd64 and linux/arm64. The arm64 version's DISK USAGE is smaller because this host never runs it, so it isn't unpacked.
With older Docker versions, or without the containerd image store, the default builder can't store multi-platform images. In that case create a builder with the
docker-containerdriver first:docker buildx create --name multi --driver docker-container --use, and add--pushto the build to push straight to a registry.
Tested: running on different architectures
On the amd64 host, Docker picks the amd64 version automatically:
$ docker run -d --name goapp -p 8080:8080 goapp:1.0
2d1485d9ba48cf1b87983ff4d797699dde128295880f1fe965f24801da611ac4
$ curl -s http://localhost:8080/
Hello from linux/amd64
$ docker logs goapp
Listening on :8080 (linux/amd64)
$ docker rm -f goapp
goapp
The program reports linux/amd64.
Forcing the arm64 version reproduces the error from the beginning:
$ docker run --rm --platform linux/arm64 goapp:1.0
exec /app: exec format error
$ docker run --rm --platform linux/arm64 alpine:3.23 uname -m
Unable to find image 'alpine:3.23' locally
3.23: Pulling from library/alpine
Digest: sha256:85fe1e81d6758c208f3e1eed4338a1997e19d4be002d4dd32d3100c9a8c010a0
Status: Image is up to date for alpine:3.23
exec /bin/uname: exec format error
- Our arm64 binary on an amd64 host:
exec format error. - The official
alpine:3.23behaves the same: Docker downloads the arm64 version, but the host CPU can't run it.
Check an image's architecture with docker image inspect; add --platform to inspect the version for a specific platform:
$ docker image inspect --format "{{.Os}}/{{.Architecture}}" goapp:1.0
linux/amd64
$ docker image inspect --platform linux/arm64 --format "{{.Os}}/{{.Architecture}}" goapp:1.0
linux/arm64
$ uname -m
x86_64
When you need the QEMU emulator
The cross-compilation above needs no emulation because the build runs no programs for the target platform. It's different when the Dockerfile runs commands (RUN) in an image for the target platform:
FROM alpine:3.23
RUN apk add --no-cache curl
$ docker buildx build --platform linux/arm64 -t arm-test .
[+] Building 0.5s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 82B 0.0s
=> [internal] load metadata for docker.io/library/alpine:3.23 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/2] FROM docker.io/library/alpine:3.23@sha256:85fe1e81d6758c208f3e1eed4338a1997e19d4be002d4dd32d3100c9a8c010a0 0.1s
=> => resolve docker.io/library/alpine:3.23@sha256:85fe1e81d6758c208f3e1eed4338a1997e19d4be002d4dd32d3100c9a8c010a0 0.0s
=> => extracting sha256:ace1621be7ff15b54252f68393ac33181df7f3e095e36a5d9a9892031b357d31 0.1s
=> ERROR [2/2] RUN apk add --no-cache curl 0.3s
------
> [2/2] RUN apk add --no-cache curl:
0.250 exec /bin/sh: exec format error
------
Dockerfile:2
--------------------
1 | FROM alpine:3.23
2 | >>> RUN apk add --no-cache curl
3 |
--------------------
ERROR: failed to build: failed to solve: process "/bin/sh -c apk add --no-cache curl" did not complete successfully: exit code: 255
RUN apk add has to run /bin/sh in arm64 Alpine, which the amd64 host can't do, so it's exec format error again. Now you need the QEMU emulator so the amd64 host can run arm64 programs:
-
Docker Desktop (Windows, macOS): QEMU is built in. No setup needed; just build with
--platform. -
Linux: install it with:
docker run --privileged --rm tonistiigi/binfmt --install allThis command registers the emulators in the host kernel, so it affects the whole host (until the next reboot). Run it only on machines you manage. The test environment for this tutorial doesn't have it installed, which is why the error above appears.
Emulation can be several times slower than native, especially for steps that compile code (for example npm ci building native modules, or pip install compiling C extensions). So use cross-compilation for languages that support it (Go, Rust), and for other languages consider native arm64 runners in CI.
Pushing a multi-platform image
When you push to a registry, both platforms are uploaded together. This demo uses a local registry (registry:3; see Pushing to Docker Hub):
$ docker tag goapp:1.0 localhost:5000/goapp:1.0
$ docker push localhost:5000/goapp:1.0
The push refers to repository [localhost:5000/goapp]
49af9741d5c4: Pushed
2cc7ee286bf3: Pushed
c172f21841df: Pushed
7c12895b777b: Pushed
44136fa355b3: Pushed
f64822ab576c: Pushed
218cf840d0d9: Pushed
f6069939f718: Pushed
d6b1b89eccac: Pushed
2780920e5dbf: Pushed
3214acf345c0: Pushed
52630fc75a18: Pushed
dd64bf2dd177: Pushed
b839dfae01f6: Pushed
ebddc55facdc: Pushed
c4bc6f35ff5e: Pushed
3febe656e709: Pushed
6ed1ef0cb115: Pushed
7f6c568ae1e4: Pushed
401d177fd1e8: Pushed
1.0: digest: sha256:dc8d3f71a9f7adb0de32dd59651adc81dab5b78612cdc7eccc39a5f73608d37e size: 1609
Check the manifest in the registry with docker buildx imagetools inspect:
$ docker buildx imagetools inspect localhost:5000/goapp:1.0
Name: localhost:5000/goapp:1.0
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:dc8d3f71a9f7adb0de32dd59651adc81dab5b78612cdc7eccc39a5f73608d37e
Manifests:
Name: localhost:5000/goapp:1.0@sha256:bae3f4b45be6e6c37bbe5430c1fd68cf98974b82dd312b81fcf568abb321584c
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/amd64
Name: localhost:5000/goapp:1.0@sha256:d6cd4f43dd3d332129c7b6b9f6720c6da06e070125860d2ab0661e15d251133a
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/arm64
Name: localhost:5000/goapp:1.0@sha256:379134f8a7c50a77e0bf19cbd9593a884daeac7eb2b7c852ccfa8f81ce7bdd34
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: unknown/unknown
Annotations:
vnd.docker.reference.digest: sha256:bae3f4b45be6e6c37bbe5430c1fd68cf98974b82dd312b81fcf568abb321584c
vnd.docker.reference.type: attestation-manifest
Name: localhost:5000/goapp:1.0@sha256:2f4085c9f5da45df6153b09141bf7db573806a93a2ed81c64c2ec75e49bfa8e6
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: unknown/unknown
Annotations:
vnd.docker.reference.digest: sha256:d6cd4f43dd3d332129c7b6b9f6720c6da06e070125860d2ab0661e15d251133a
vnd.docker.reference.type: attestation-manifest
- At the top is an image index (
application/vnd.oci.image.index.v1+json), the "table of contents" of a multi-platform image. - Under it are two images,
linux/amd64andlinux/arm64. - The two
unknown/unknownentries are attestations (build provenance) generated automatically by BuildKit, one for each platform. They are not runnable images.
Pushing to Docker Hub works the same way; just use the name your-account/goapp:1.0. After that, docker pull on a host of any architecture gets the matching version.
Building only the target platform
If you only need to build an image on an Apple Silicon Mac for an amd64 server, you don't need a multi-platform image. Just specify the target platform:
docker build --platform linux/amd64 -t yourname/myapp:1.0 .
docker push yourname/myapp:1.0
Docker Desktop emulates amd64 with its built-in QEMU. It's slower than native, but it works.
Recap
- An image must match the host's CPU architecture, or you get
exec format error. - A multi-platform image is an index with a version for each platform underneath.
- Cross-compiling with
FROM --platform=$BUILDPLATFORM+TARGETOS/TARGETARCHneeds no emulation. - Docker Desktop includes QEMU; on Linux you install binfmt.
Exercises
Exercise 1: Use docker buildx imagetools inspect to find which platforms postgres:18 supports.
Answer
docker buildx imagetools inspect postgres:18 | grep Platform: | grep -v unknown
unknown/unknown entries are build attestations, not runnable images, so they are excluded.
FAQ
My Mac has Apple Silicon. What should I watch out for when deploying to a cloud server?
Add --platform linux/amd64 when building, or build a multi-platform image. Otherwise the server shows exec format error.
Do multi-platform builds always need QEMU?
Only when the build has to run programs on the target platform (for example RUN npm ci). Languages that cross-compile, such as Go and Rust, don't need it.
How do I find out which platforms an image supports?
docker buildx imagetools inspect IMAGE. In this chapter the nginx example lists 8 platforms.
Updated October 14, 2026