Buildx multi-platform para un agente IA: amd64 y arm64 en un tag
Resumen
Cómo publicar un agente que corre en Mac ARM y en VPS x86 con un solo tag: docker buildx --platform linux/amd64,linux/arm64, driver docker-container, QEMU vs nodos nativos vs cross-compile, y GitHub Actions con setup-qemu + setup-buildx. Distinto de Bake, de cache y de pin digest.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
Un docker build en tu Mac M-series produce linux/arm64. El VPS de self-hosting es linux/amd64. Sin un manifest list, docker pull en el VPS falla o cae a emulación lenta. Multi-platform no es “compilar dos veces a mano”: es un tag que apunta a dos manifiestos y el daemon elige el de la máquina.
No es Bake (eso declara targets). No es cache de capas (eso acelera el mismo Dockerfile). No es pin de digest (eso fija qué SHA tiras; aquí fijas para qué CPU). Hub: seguridad, coste y operación.
La regla
Un tag, dos (o más) plataformas. El registry guarda el índice más cada variante. El cliente pide ghcr.io/org/agente:1.4 y recibe linux/arm64 en el Mac y linux/amd64 en el VPS. Si publicas solo la arquitectura del laptop, el agente “funciona en mi máquina” y explota en Fly/Railway/Hetzner.
El comando mínimo, con Buildx:
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ghcr.io/org/agente:1.4 \
--push .
--push no es opcional si el driver es docker-container: esa imagen no se carga sola en el store clásico. Docker Desktop y Engine 29+ con containerd image store sí pueden cargar el índice; el runner default de GitHub Actions no.
Tres estrategias (elige una)
Docker documenta tres vías. Para un agente Node/Python con pnpm --prod o pip, no son intercambiables.
| Estrategia | Cuándo | Costo |
|---|---|---|
| QEMU (emulación) | Dockerfile sin cambios; Desktop ya trae QEMU | Fácil. Lento en npm ci, compiladores nativos, Playwright |
| Nodos nativos | Un builder con un nodo amd64 y otro arm64 (o Docker Build Cloud) | Rápido. Overhead de cluster |
| Cross-compile | Go/Rust/Zig con BUILDPLATFORM / TARGETPLATFORM | El FROM --platform=$BUILDPLATFORM evita QEMU en el stage de build |
QEMU se instala a mano solo si el BuildKit no trae emuladores (paquete de terceros). El one-liner oficial es docker run --privileged --rm tonistiigi/binfmt --install all. Verifica flags F en /proc/sys/fs/binfmt_misc/qemu-*. En Desktop no hace falta.
Cross-compile (docs oficiales, 7 sep 2026):
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM node:22-alpine AS deps
ARG TARGETPLATFORM
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile --prod
$BUILDPLATFORM es la CPU del builder; $TARGETPLATFORM es linux/arm64 o linux/amd64. Si el RUN ejecuta binarios del target (un node-gyp nativo), esto no sustituye QEMU: o compilas cruzado de verdad, o dejas que emule.

Builder: no uses el default a ciegas
El builder docker (el de docker build clásico) no produce índices multi-arch de forma fiable en Engine viejo. Crea uno con driver docker-container:
docker buildx create \
--name container-builder \
--driver docker-container \
--bootstrap --use
Ese driver no carga el resultado al docker images local. Eso no es un bug: es el contrato. Para probar en el laptop usa --load con una sola --platform (la tuya). Para publicar, --push al registry.
Nodos nativos, si ya tienes contextos node-amd64 y node-arm64:
docker buildx create --use --name mybuild node-amd64
docker buildx create --append --name mybuild node-arm64
docker buildx build --platform linux/amd64,linux/arm64 --push .
No improvises un segundo nodo en la misma Mac con QEMU y lo llames “nativo”. Nativo = hardware de esa arch.
GitHub Actions: el patrón que no inventes
Docs de Docker para GHA (consultadas el 7 de septiembre de 2026). Acciones actuales: docker/setup-qemu-action@v4, docker/setup-buildx-action@v4, docker/build-push-action@v7, docker/login-action@v4.
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: docker/login-action@v4
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v4
- uses: docker/build-push-action@v7
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/org/agente:1.4
Tres trampas:
load: true+ dos plataformas en el runner default falla. El store local no traga el índice. Si el job necesita la imagen para un test, o habilitas containerd snapshotter condocker/setup-docker-action, o construyes una plataforma (linux/amd64enubuntu-latest) para el test y el job de publish hace el índice.- QEMU en
npm ci/ Playwright / Chromium infla el job 3–10×. Si el Dockerfile instala browsers, parte el matrix: un runnerubuntu-latest(amd64) y otroubuntu-24.04-arm(arm64) y luego un merge. Docker documentadocker/github-builderpara no inventar el job de manifiesto. user/app:latestsin digest. El índice también se pineá: publica el tag y guarda el digest del índice (no el de una sola arch). El pin vive en la guía de digest.

Agente concreto: qué construir
Un bot de Telegram en Node que corre en Fly (amd64) y en un Mac Mini de staging (arm64) necesita las mismas capas de runtime, no un Dockerfile “si Darwin”. Receta:
- Multi-stage: stage
deps+ stagerunnerconUSER node. --platform linux/amd64,linux/arm64solo en CI. Local:docker buildx build --platform linux/arm64 --load(o amd64, la tuya).- Healthcheck con
node -e, nowget(Alpine no lo trae). - No copies
node_modulesdel host: el lockfile sí, el install corre dentro de cada plataforma.
Si el agente usa Chromium, QEMU duele. Ahí gana el matrix nativo o un binario de Playwright por arch (npx playwright install --with-deps en cada runner), no un FROM mcr.microsoft.com/playwright emulado.
Checklist
-
docker buildx lsmuestra un builderdocker-container(o Desktop 29+ con containerd). -
--platform linux/amd64,linux/arm64y--pushal registry; no--loaddel índice en el runner default. - Local prueba una arch con
--load. - CI:
setup-qemu-action@v4+setup-buildx-action@v4+build-push-action@v7. - Jobs pesados (Playwright, compiladores) no pasan por QEMU: matrix nativo o cross-compile real.
- El tag publicado se verifica con
docker buildx imagetools inspect ghcr.io/org/agente:1.4(deben listarse ambas plataformas). - El deploy (Fly/Railway/Compose) pineá digest del índice, no un SHA de una sola arch.
FAQ
¿docker build --platform linux/amd64 en el Mac basta? Produce una imagen amd64 vía QEMU. El tag no es multi-arch. El VPS amd64 corre; el Mini ARM no, o al revés.
¿Puedo hacer dos tags :amd64 y :arm64? Sí, y es peor UX. El índice es el contrato. Dos tags obligan a que Compose/Fly elijan a mano.
¿Bake reemplaza esto? No. Bake puede pasar platforms = ["linux/amd64","linux/arm64"] por target. El índice lo sigue haciendo Buildx.
¿Por qué imagetools inspect muestra un manifest y no dos? Construiste una sola plataforma, o pusheaste con el driver docker clásico. Recrea el builder docker-container y vuelve a --push.
Fuentes oficiales Docker y actions setup-qemu / setup-buildx verificadas el 7 de septiembre de 2026.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.

Build secrets Docker para un agente IA: el token no va en el layer

Trivy para tu agente IA: escanear la imagen Docker antes de desplegar

ARG vs ENV en un agente IA: build-time no es runtime
