Cómo construir imágenes Docker multiarquitectura (amd64/arm64) con GitHub Actions
Docker & Containers · Por Kevin Marcondes · Publicado el · Actualizado el · 9 min de lectura
Este tutorial muestra, paso a paso, cómo construir imágenes Docker multiarquitectura (amd64 y arm64) y publicar manifestos con GitHub Actions. Entenderás cómo funcionan los manifiestos OCI, cómo estructurar un Dockerfile multiplataforma, configurar Buildx y QEMU en el CI, aplicar caché de BuildKit y validar que cada arquitectura obtiene la imagen correcta.
Multiarquitectura y manifiestos OCI: qué vas a publicar
Una imagen multiarquitectura es un conjunto de imágenes específicas por plataforma agrupadas bajo una misma etiqueta. El registro almacena un índice OCI, también llamado lista de manifiestos.
Cuando haces pull, el cliente escoge la variante correcta según la plataforma. De ese modo, una única etiqueta, como latest, puede apuntar a amd64 y arm64 al mismo tiempo, sin que el consumidor tenga que preocuparse.
Con Buildx, publicar para varias plataformas en un solo push crea el manifiesto automáticamente. También es posible crear o actualizar manifiestos de forma explícita cuando sea necesario.
Prerequisitos de entorno y acceso al registro
Antes de empezar, asegúrate de tener acceso al registro donde vas a publicar y un repositorio con un Dockerfile listo para construir. El runner de GitHub Actions puede ser el estándar en Linux.
El workflow instalará QEMU y Buildx para permitir builds cruzados. Guarda las credenciales del registro como secretos del repositorio para la autenticación en el pipeline.
- Cuenta en Docker Hub y/o GHCR con permisos de escritura.
- Secretos configurados en el repositorio (por ejemplo: DOCKERHUB_USERNAME/DOCKERHUB_TOKEN, o uso de GITHUB_TOKEN en GHCR).
- Dockerfile preparado para multiplataforma; evita dependencias específicas de CPU sin alternativa.
- Pipeline en GitHub Actions creado; consulta configurar pipeline de despliegue automático.
- Para imágenes más ligeras, aplica multistage; referencia: Imágenes Docker ligeras: multistage.
Estructura del Dockerfile para multiplataforma (TARGETARCH/TARGETOS)
BuildKit inyecta variables de contexto como TARGETOS, TARGETARCH y BUILDPLATFORM. Usa estas variables para compilar binarios para la arquitectura objetivo y elegir bases correctas en los stages de build.
El ejemplo abajo compila un binario Go estático por arquitectura y publica una imagen final mínima. Adapta los comandos a tu proyecto y lenguaje. Si tu build usa librerías nativas, verifica la compatibilidad con cross‑compile.
# Exemplo Dockerfile multiplataforma (Go)
# Estágio de build roda na plataforma do builder
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS build
ARG TARGETOS
ARG TARGETARCH
WORKDIR /src
# Dependências de módulos
COPY go.mod go.sum ./
RUN go mod download
# Código da aplicação
COPY . .
# Compila binário para a arquitetura alvo
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
go build -o /out/app ./cmd/app
# Imagem final mínima
FROM alpine:3.20
WORKDIR /app
COPY --from=build /out/app /usr/local/bin/app
# Labels OCI úteis para rastreabilidade
LABEL org.opencontainers.image.source="https://example.com/repo" \
org.opencontainers.image.description="App multi-arch amd64/arm64"
USER 10001
ENTRYPOINT ["app"]
Workflow de GitHub Actions: Buildx, QEMU, login y push multiarch
El workflow instala QEMU y Buildx, autentica en el registro y usa la acción build‑push para construir y publicar, en un solo paso, las variantes amd64 y arm64. El caché de BuildKit reduce el tiempo de build entre ejecuciones.
Ajusta las etiquetas según tu estrategia. La metadata‑action ayuda a generar tags por branch, tag de release y SHA automáticamente. Para GHCR, GITHUB_TOKEN suele ser suficiente para publicar paquetes del repositorio.
name: build-and-push-multiarch
on:
push:
branches: ["main"]
tags: ["v*", "v*.*.*"]
pull_request:
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta (tags e labels)
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
labels: |
org.opencontainers.image.source=${{ github.repositoryUrl }}
org.opencontainers.image.revision=${{ github.sha }}
- name: Login GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Exemplo opcional: login também no Docker Hub
# - name: Login Docker Hub
# uses: docker/login-action@v3
# with:
# username: ${{ secrets.DOCKERHUB_USERNAME }}
# password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build & Push multi-arch
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Estrategia de tags y labels que evita confusiones
Define convenciones claras para cada evento del repositorio. Usa latest solo para la rama estable principal. En releases, publica tags inmutables (semver) y, si tiene sentido, tags abreviadas para rangos.
Incluye labels OCI para trazabilidad de origen y commit. Evita sobrescribir tags de versiones antiguas. Mantén la misma lista de plataformas en todas las tags estables.
- Ramas principales: latest y SHA del commit.
- Releases: vX.Y.Z, vX.Y, vX y latest (si representa la versión estable actual).
- Pull requests: tags efímeras (por ejemplo, pr-<número>) sin promover latest.
- Incluye labels org.opencontainers.image.source y revision para auditoría.
| Evento | Tags recomendadas |
|---|---|
| Push en main | latest, sha-<CORTO> |
| Tag v1.2.3 | v1.2.3, v1.2, v1, latest (opcional) |
| Pull request | pr-<número>, sha-<CORTO> (sin latest) |
Publicar e inspeccionar el manifiesto generado
Al construir con múltiples plataformas en Buildx y hacer push, el manifiesto se crea automáticamente. Inspecciona el resultado para comprobar que amd64 y arm64 figuran en la misma etiqueta.
Usa la herramienta de inspección para verificar plataformas, digests y capas. Esto ayuda a diagnosticar pulls que fallan por ausencia de una variante específica.
# Inspecione a manifest list publicada
# Ajuste a referência para seu registro e tag
docker buildx imagetools inspect ghcr.io/<owner>/<repo>:<tag>
# Opcional: inspecione um digest específico
# docker buildx imagetools inspect ghcr.io/<owner>/<repo>@sha256:<digest>
Cómo confirmar en cada arquitectura (amd64/arm64)
Valida la ejecución forzando la plataforma en el cliente. Cada comando debe imprimir la arquitectura del kernel dentro del contenedor. Así, garantizas que se descargó la imagen correcta desde la misma etiqueta.
Si el binario depende de librerías nativas, ejecuta una prueba rápida de funcionalidad en cada plataforma para anticipar problemas en tiempo de ejecución.
# Força execução como amd64
docker run --rm --platform linux/amd64 ghcr.io/<owner>/<repo>:<tag> uname -m
# Força execução como arm64
docker run --rm --platform linux/arm64 ghcr.io/<owner>/<repo>:<tag> uname -m
Errores comunes, diagnósticos y optimizaciones de caché
Algunos problemas aparecen en builds multiplataforma. Abajo tienes una guía rápida de diagnóstico y solución. Para reducir el tiempo de build, usa caché remoto de BuildKit y basa capas en archivos estables.
Para técnicas avanzadas de caché en CI, consulta la guía dedicada: Cómo usar BuildKit y caché remota en CI Docker.
- no matching manifest: la etiqueta no incluye la plataforma deseada.
- exec format error: se copió un binario de la arquitectura equivocada en la imagen final.
- Fallo al registrar QEMU: el runner no cargó binfmt; asegúrate de ejecutar setup-qemu en el workflow.
- Cross‑compile con librerías nativas: ajusta toolchains y flags para cada TARGETARCH.
- Caché ineficiente: reorganiza capas y activa cache-to/cache-from de BuildKit.
| Síntoma | Causa probable | Cómo resolver |
|---|---|---|
| no matching manifest para arm64 | Etiqueta publicada solo con amd64 | Asegura platforms: linux/amd64,linux/arm64 en el build y republica |
| exec format error al iniciar | Binario amd64 copiado en imagen arm64 | Usa TARGETOS/TARGETARCH en el build y COPY del artefacto correcto |
| Build lento en PRs | Caché no compartido o inválido | Activa cache gha y fija pasos COPY para maximizar reutilización |
| Error al ejecutar toolchain nativo en cross | Dependencias específicas de CPU | Usa compilaciones estáticas o toolchains multiplataforma apropiados |
Preguntas frecuentes
¿Necesito runners arm64 para construir imágenes arm64?
En la mayoría de los casos, no. Docker Buildx con QEMU permite construir variantes arm64 desde un runner amd64. Los builds nativos pueden ser más rápidos y necesarios en proyectos con dependencias de sistema específicas, pero para muchas aplicaciones el cross‑build funciona correctamente.
¿Buildx ya publica el manifiesto o necesito crearlo manualmente?
Si usas la acción build‑push con múltiples plataformas y push habilitado, el manifiesto se crea automáticamente en la etiqueta indicada. La creación manual solo es necesaria para componer manifiestos usando imágenes ya existentes o para retagging específico.
¿Cómo elegir entre Docker Hub y GHCR para publicar?
Ambos funcionan. Evalúa permisos, la integración con repositorios y control de acceso. Puedes publicar en ambos autenticándote en los dos registros y usando dos referencias de etiqueta en el mismo build. Ajusta secretos y permisos según la política del equipo.
¿Cómo reducir el tamaño de la imagen final multi‑arch?
Usa multistage para conservar solo el artefacto necesario, elige bases mínimas y elimina herramientas de build en el stage final. Prefiere binarios estáticos cuando sea posible y ordena COPY/RUN para maximizar caché. El mismo Dockerfile puede atender amd64 y arm64 sin duplicación.
¿Puedo tener tags diferentes por arquitectura?
Puedes, pero es poco habitual y aumenta la complejidad. El patrón recomendado es una etiqueta única que apunte a un manifiesto que referencia las variantes por plataforma. Usa tags por arquitectura solo cuando haya diferencias funcionales que requieran separación explícita.
¿QEMU es suficiente para proyectos con dependencias nativas (por ejemplo, C/C++)?
Depende del proyecto. QEMU cubre muchos casos, pero toolchains y librerías nativas pueden exigir configuración adicional o builds nativos. Valora compilar estáticamente cuando sea posible y mantén pruebas por arquitectura para validar los binarios resultantes.
¿Listo para publicar imágenes multi‑arch con confianza?
Si quieres que revisemos tu Dockerfile y tu workflow de release multiplataforma en GitHub Actions, envía un mensaje con el repositorio y el objetivo de tus tags.
Quiero revisar mi workflowVer también
- Tutorial: implementar Canary releases con Traefik en Docker Compose usando headers y weighted routing para despliegues graduales de aplicaciones PHP
- Tutorial: configurar HAProxy con Let's Encrypt y healthchecks para balanceo de tráfico entre contenedores Docker en VPS (paso a paso)
- Tutorial práctico: implementar mTLS entre Nginx y microservicios usando smallstep CA en Docker Compose
- Tutorial: configurar cgroup v2 y systemd ResourceControl para limitar CPU, memoria e I/O de contenedores Docker en Debian