目錄
建置多平台映像檔(amd64 與 arm64):docker buildx 教學
問題:exec format error
在 Apple Silicon(M 系列晶片)的 Mac 上建置映像檔,推送到一般的雲端主機執行,容器一啟動就退出:
exec /app: exec format error
原因是 CPU 架構不同。Apple Silicon Mac 預設建置的是 arm64 映像檔,而多數雲端主機是 amd64(x86_64)。程式被編譯成某一種 CPU 的機器碼,另一種 CPU 看不懂,就會出現這個錯誤。後面會實際重現。
常見的平台
| 平台 | 常見設備 |
|---|---|
linux/amd64 | 大部分雲端主機、Intel / AMD 電腦 |
linux/arm64 | Apple Silicon Mac、AWS Graviton、Google Axion、樹莓派 4/5(64 位元系統) |
linux/arm/v7 | 舊款樹莓派(32 位元系統) |
什麼是多平台映像檔
官方映像檔(例如 nginx、node)是多平台映像檔:同一個名稱和 tag 底下,存放了多個架構的版本。查看 nginx:1.30-alpine 支援哪些平台:
$ 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
同一個 nginx:1.30-alpine 包含 8 種架構。docker pull 時,Docker 會自動挑選符合目前主機的版本,所以同一個 docker run nginx:1.30-alpine 在 Mac 和雲端主機上都能執行。我們也可以讓自己的映像檔這樣做。
準備範例:Go 網頁服務
用一個會顯示自己執行架構的 Go 程式來示範。資料夾 goapp:
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"]
這是一個交叉編譯的 Dockerfile,關鍵在幾個 BuildKit 自動提供的參數:
| 參數 | 意思 | 本例的值 |
|---|---|---|
BUILDPLATFORM | 執行建置的機器的平台 | linux/amd64 |
TARGETPLATFORM | 要產出的映像檔的平台 | linux/amd64 或 linux/arm64 |
TARGETOS / TARGETARCH | 目標平台拆開的作業系統與架構 | linux / amd64 或 arm64 |
FROM --platform=$BUILDPLATFORM golang:1.27:編譯階段永遠使用建置機器本身的架構執行,速度是原生的。GOOS=$TARGETOS GOARCH=$TARGETARCH:讓 Go 編譯器直接產出目標架構的執行檔。- 最後一個階段只是
COPY,不執行任何程式,所以不需要模擬目標 CPU。
用 buildx 一次建置兩個平台
docker buildx 是使用 BuildKit 的建置指令,Docker Desktop 與 Linux 的 docker-buildx-plugin 都已內建。先看看目前的 builder:
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default* docker
\_ default \_ default running v0.34.0 linux/amd64 (+3)
Docker 29 預設使用 containerd 映像檔儲存,預設的 docker builder 就能建置多平台映像檔並存在本機。用 --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
觀察建置過程:
- 每個步驟前面標示了平台,例如
[linux/amd64 build 4/4]。 - arm64 的編譯步驟顯示為
[linux/amd64->arm64 build 4/4]:在 amd64 機器上,為 arm64 編譯。這就是交叉編譯。 - 兩個平台的
go build平行執行。
用 docker images --tree 查看結果(新版 Docker 才有 --tree 選項):
$ 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 底下有 linux/amd64 和 linux/arm64 兩個版本。arm64 版本的 DISK USAGE 比較小,是因為本機不會執行它,所以沒有解壓縮。
較舊的 Docker 版本或沒有啟用 containerd 映像檔儲存時,預設 builder 無法存放多平台映像檔。這時要先建立一個
docker-container驅動的 builder:docker buildx create --name multi --driver docker-container --use,並在建置時加上--push直接推送到 Registry。
實測:在不同架構上執行
在 amd64 主機上執行,Docker 自動挑選 amd64 版本:
$ 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
程式回報自己是 linux/amd64。
強制執行 arm64 版本,就重現了開頭的錯誤:
$ 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
- 我們的 arm64 執行檔在 amd64 主機上:
exec format error。 - 官方的
alpine:3.23也一樣:Docker 下載了 arm64 版本,但主機 CPU 無法執行。
用 docker image inspect 確認映像檔的架構,加上 --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
什麼時候需要 QEMU 模擬器
上面的交叉編譯之所以不需要模擬,是因為建置過程中沒有執行任何目標平台的程式。如果 Dockerfile 在目標平台的映像檔中執行指令(RUN),情況就不同了:
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 需要在 arm64 的 Alpine 中執行 /bin/sh,amd64 主機無法執行,同樣是 exec format error。這時需要 QEMU 模擬器,讓 amd64 主機能執行 arm64 程式:
-
Docker Desktop(Windows、macOS):已內建 QEMU,不需要設定,直接用
--platform建置即可。 -
Linux:執行以下指令安裝:
docker run --privileged --rm tonistiigi/binfmt --install all這個指令會在主機核心註冊模擬器,影響整台主機(重新開機後失效),請在自己管理的機器上執行。本教學的測試環境沒有安裝,所以才會重現上面的錯誤。
模擬執行的速度可能比原生慢好幾倍,尤其是需要編譯的步驟(例如 npm ci 編譯原生模組、pip install 編譯 C 擴充)。所以能交叉編譯的語言(Go、Rust)盡量用交叉編譯;其他語言可以考慮在 CI 中使用原生的 arm64 執行環境。
推送多平台映像檔
推送到 Registry 後,兩個平台會一起上傳。這裡用本機的 Registry(registry:3,參考 推送到 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
用 docker buildx imagetools inspect 檢查 Registry 上的 manifest:
$ 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
- 最上層是一個 image index(
application/vnd.oci.image.index.v1+json),也就是多平台映像檔的「目錄」。 - 底下有
linux/amd64和linux/arm64兩個映像檔。 - 另外兩個
unknown/unknown是 BuildKit 自動產生的 attestation(建置來源證明),分別對應兩個平台,不是可執行的映像檔。
推送到 Docker Hub 的指令也一樣,只是名稱換成 你的帳號/goapp:1.0。之後在任何架構的主機上 docker pull 都會拿到對應的版本。
只建置目標平台
如果只是要在 Apple Silicon Mac 上建置給 amd64 伺服器用的映像檔,不需要多平台,指定目標平台即可:
docker build --platform linux/amd64 -t yourname/myapp:1.0 .
docker push yourname/myapp:1.0
Docker Desktop 會用內建的 QEMU 模擬 amd64,速度比原生慢,但可以正常運作。
重點回顧
- 映像檔要符合主機的 CPU 架構,否則出現
exec format error。 - 多平台映像檔是一個 index,底下有各平台的版本。
FROM --platform=$BUILDPLATFORM+TARGETOS/TARGETARCH交叉編譯不需要模擬。- Docker Desktop 內建 QEMU;Linux 需要安裝 binfmt。
動手練習
練習 1:用 docker buildx imagetools inspect 查詢 postgres:18 支援哪些平台。
答案
docker buildx imagetools inspect postgres:18 | grep Platform: | grep -v unknown
unknown/unknown 是建置證明(attestation),不是可執行的映像檔,所以排除。
常見問題
我的 Mac 是 Apple Silicon,部署到雲端主機要注意什麼?
建置時加上 --platform linux/amd64,或建置多平台映像檔,否則伺服器上會出現 exec format error。
多平台建置一定要 QEMU 嗎?
只有建置過程需要在目標平台上執行程式(例如 RUN npm ci)時才需要。Go、Rust 這類能交叉編譯的語言不需要。
怎麼知道一個映像檔支援哪些平台?
docker buildx imagetools inspect 映像檔,本章以 nginx 為例列出了 8 種平台。
更新於 2026年10月14日