Contenido

Tutorial de imágenes multiplataforma en Docker: compila para amd64 y arm64 con docker buildx

Problema: exec format error

Compilas una imagen en un Mac con Apple Silicon (chip de la serie M), la subes a un host en la nube común y, al iniciar, el contenedor se cierra de inmediato:

exec /app: exec format error

La causa es que la arquitectura de la CPU es distinta. De forma predeterminada, un Mac con Apple Silicon compila imágenes arm64, mientras que la mayoría de los hosts en la nube usan amd64 (x86_64). Un programa se compila en código máquina para una CPU concreta; otra CPU no puede interpretarlo y aparece este error. Más adelante lo reproduciremos en la práctica.

Plataformas habituales

PlataformaDispositivos habituales
linux/amd64La mayoría de los hosts en la nube y los equipos Intel / AMD
linux/arm64Mac con Apple Silicon, AWS Graviton, Google Axion, Raspberry Pi 4/5 (sistema de 64 bits)
linux/arm/v7Modelos antiguos de Raspberry Pi (sistema de 32 bits)

Qué es una imagen multiplataforma

Las imágenes oficiales (por ejemplo, nginx y node) son imágenes multiplataforma: bajo el mismo nombre y tag se guardan versiones para varias arquitecturas. Consulta qué plataformas admite 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 incluye 8 arquitecturas. Al ejecutar docker pull, Docker selecciona automáticamente la versión compatible con el host actual. Por eso, el mismo comando docker run nginx:1.30-alpine funciona tanto en un Mac como en un host en la nube. También puedes hacer lo mismo con tus propias imágenes.

Prepara el ejemplo: servicio web en Go

Para la demostración, usaremos un programa en Go que muestra la arquitectura en la que se ejecuta. La carpeta 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"]

Este es un Dockerfile de compilación cruzada. Estas son algunas de las variables que BuildKit proporciona automáticamente:

VariableSignificadoValor en este ejemplo
BUILDPLATFORMPlataforma de la máquina que realiza la compilaciónlinux/amd64
TARGETPLATFORMPlataforma de la imagen que se va a generarlinux/amd64 o linux/arm64
TARGETOS / TARGETARCHSistema operativo y arquitectura de destino, por separadolinux / amd64 o arm64
  • FROM --platform=$BUILDPLATFORM golang:1.27: la etapa de compilación siempre se ejecuta con la arquitectura de la propia máquina que compila, a velocidad nativa.
  • GOOS=$TARGETOS GOARCH=$TARGETARCH: indica al compilador de Go que genere directamente un ejecutable para la arquitectura de destino.
  • La última etapa solo hace COPY; no ejecuta ningún programa, así que no hace falta emular la CPU de destino.

Construye para dos plataformas con buildx

docker buildx es el comando de compilación que usa BuildKit. Docker Desktop y docker-buildx-plugin en Linux ya lo incluyen. Primero, comprueba el builder actual:

$ docker buildx ls
NAME/NODE     DRIVER/ENDPOINT   STATUS    BUILDKIT   PLATFORMS
default*      docker
 \_ default    \_ default       running   v0.34.0    linux/amd64 (+3)

Docker 29 usa containerd de forma predeterminada para almacenar imágenes, así que el builder docker predeterminado puede compilar imágenes multiplataforma y guardarlas localmente. Usa --platform para especificar varias plataformas:

$ 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

Observa el proceso de compilación:

  • Cada paso indica la plataforma al principio, por ejemplo, [linux/amd64 build 4/4].
  • El paso de compilación para arm64 aparece como [linux/amd64->arm64 build 4/4]: compila para arm64 en una máquina amd64. Esto es compilación cruzada.
  • go build se ejecuta en paralelo para ambas plataformas.

Usa docker images --tree para ver el resultado (la opción --tree solo está disponible en las versiones recientes de Docker):

$ 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

Debajo de goapp:1.0 hay dos versiones: linux/amd64 y linux/arm64. El valor de DISK USAGE de la versión arm64 es menor porque la máquina local no la ejecuta y, por tanto, no la descomprime.

En versiones anteriores de Docker, o cuando el almacenamiento de imágenes de containerd no está habilitado, el builder predeterminado no puede almacenar imágenes multiplataforma. En ese caso, primero crea un builder con el controlador docker-container: docker buildx create --name multi --driver docker-container --use, y añade --push durante la compilación para enviar la imagen directamente al Registry.

Prueba: ejecutar en distintas arquitecturas

En un host amd64, Docker selecciona automáticamente la versión 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

El programa indica que es linux/amd64.

Al forzar la ejecución de la versión arm64, se reproduce el error del principio:

$ 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
  • Nuestro ejecutable arm64 en un host amd64: exec format error.
  • Lo mismo ocurre con la imagen oficial alpine:3.23: Docker descargó la versión arm64, pero la CPU del host no puede ejecutarla.

Usa docker image inspect para comprobar la arquitectura de la imagen. Añade --platform para consultar la versión de una plataforma específica:

$ 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

Cuándo necesitas el emulador QEMU

La compilación cruzada anterior no necesita emulación porque durante el proceso de compilación no se ejecuta ningún programa de la plataforma de destino. Si el Dockerfile ejecuta un comando (RUN) dentro de una imagen de la plataforma de destino, la situación es distinta:

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 necesita ejecutar /bin/sh en Alpine arm64, pero el host amd64 no puede hacerlo y aparece el mismo error exec format error. En este caso necesitas el emulador QEMU para que el host amd64 pueda ejecutar programas arm64:

  • Docker Desktop (Windows, macOS): QEMU ya está integrado, no necesitas configurarlo. Basta con compilar usando --platform.

  • Linux: ejecuta el siguiente comando para instalarlo:

    docker run --privileged --rm tonistiigi/binfmt --install all

    Este comando registra el emulador en el kernel del host y afecta a todo el host (deja de estar activo después de reiniciar). Ejecútalo en una máquina que administres. El entorno de pruebas de este tutorial no lo tiene instalado; por eso se reproduce el error anterior.

La ejecución emulada puede ser varias veces más lenta que la nativa, sobre todo en pasos que requieren compilación (por ejemplo, npm ci compila módulos nativos y pip install compila extensiones C). Por eso, cuando sea posible, usa compilación cruzada con lenguajes que la admitan (Go, Rust); para otros lenguajes, considera usar un entorno de ejecución arm64 nativo en CI.

Publicar imágenes multiplataforma

Al publicar en un Registry, se suben ambas plataformas a la vez. Aquí usamos un Registry local (registry:3) como ejemplo (consulta Publicar en 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

Usa docker buildx imagetools inspect para inspeccionar el manifest del Registry:

$ 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
  • La capa superior es un image index (application/vnd.oci.image.index.v1+json), es decir, el «directorio» de las imágenes multiplataforma.
  • Debajo hay imágenes para linux/amd64 y linux/arm64.
  • Los otros dos unknown/unknown son attestation (pruebas del origen de la compilación) generadas automáticamente por BuildKit, una para cada plataforma; no son imágenes ejecutables.

El comando para publicar en Docker Hub es el mismo; solo cambia el nombre por tu-cuenta/goapp:1.0. Después, al ejecutar docker pull en un host de cualquier arquitectura, obtendrás la versión correspondiente.

Compilar solo para la plataforma de destino

Si solo quieres compilar en un Mac con Apple Silicon una imagen para un servidor amd64, no necesitas una compilación multiplataforma: basta con especificar la plataforma de destino:

docker build --platform linux/amd64 -t yourname/myapp:1.0 .
docker push yourname/myapp:1.0

Docker Desktop usa QEMU integrado para emular amd64. Es más lento que ejecutarlo de forma nativa, pero funciona correctamente.

Resumen

  • La imagen debe coincidir con la arquitectura de CPU del host; de lo contrario, aparecerá exec format error.
  • Una imagen multiplataforma es un index que contiene versiones para cada plataforma.
  • La compilación cruzada con FROM --platform=$BUILDPLATFORM + TARGETOS/TARGETARCH no requiere emulación.
  • Docker Desktop incluye QEMU; en Linux tienes que instalar binfmt.

Ejercicios

Ejercicio 1: usa docker buildx imagetools inspect para consultar qué plataformas admite postgres:18.

Respuesta
docker buildx imagetools inspect postgres:18 | grep Platform: | grep -v unknown

unknown/unknown es una prueba de compilación (attestation), no una imagen ejecutable, así que se excluye.

Preguntas frecuentes

Mi Mac tiene Apple Silicon. ¿Qué debo tener en cuenta al desplegar en un host en la nube?

Añade --platform linux/amd64 al compilar o crea una imagen multiplataforma; de lo contrario, aparecerá exec format error en el servidor.

¿QEMU es imprescindible para compilar para varias plataformas?

Solo lo necesitas si durante la compilación hay que ejecutar programas en la plataforma de destino (por ejemplo, RUN npm ci). No hace falta con lenguajes que permiten compilación cruzada, como Go y Rust.

¿Cómo puedo saber qué plataformas admite una imagen?

Ejecuta docker buildx imagetools inspect imagen; este capítulo muestra 8 plataformas usando nginx como ejemplo.

Actualizado 14 de octubre de 2026