Docker pour les devs - 25 - Builds multi-platform

ARM vs x86, docker buildx, images multi-architecture, emulation QEMU et builds multi-platform en CI.

  1. 01 Docker pour les devs - 00 - Pourquoi Docker change tout
  2. 02 Docker pour les devs - 01 - Containers vs VMs
  3. 03 Docker pour les devs - 02 - L'architecture de Docker
  4. 04 Docker pour les devs - 03 - Docker Desktop, Engine et alternatives
  5. 05 Docker pour les devs - 04 - Écrire un Dockerfile
  6. 06 Docker pour les devs - 05 - Layers et cache
  7. 07 Docker pour les devs - 06 - Le .dockerignore
  8. 08 Docker pour les devs - 07 - Multi-stage builds
  9. 09 Docker pour les devs - 08 - Choisir son image de base
  10. 10 Docker pour les devs - 09 - Docker Compose, les bases
  11. 11 Docker pour les devs - 10 - Docker Compose avance
  12. 12 Docker pour les devs - 11 - Networking Docker, les bases
  13. 13 Docker pour les devs - 12 - Networking Docker avance
  14. 14 Docker pour les devs - 13 - Volumes et persistance
  15. 15 Docker pour les devs - 14 - Variables d'environnement et secrets
  16. 16 Docker pour les devs - 15 - Permissions et utilisateurs
  17. 17 Docker pour les devs - 16 - Docker et monorepo
  18. 18 Docker pour les devs - 17 - Dev vs Prod
  19. 19 Docker pour les devs - 18 - ENTRYPOINT, CMD et scripts d'initialisation
  20. 20 Docker pour les devs - 19 - Debugger ses conteneurs
  21. 21 Docker pour les devs - 20 - Bases de donnees dans Docker
  22. 22 Docker pour les devs - 21 - Sauvegardes et restauration
  23. 23 Docker pour les devs - 22 - Conteneuriser un frontend
  24. 24 Docker pour les devs - 23 - Sécurité des conteneurs
  25. 25 Docker pour les devs - 24 - Optimisation des images
  26. 26 Docker pour les devs - 25 - Builds multi-platform
  27. 27 Docker pour les devs - 26 - Limiter les ressources de tes conteneurs
  28. 28 Docker pour les devs - 27 - Gerer les logs comme un adulte
  29. 29 Docker pour les devs - 28 - Healthchecks et restart policies
  30. 30 Docker pour les devs - 29 - Nettoyer Docker avant qu'il mange ton disque
  31. 31 Docker pour les devs - 30 - Registries et stratégie de tags
  32. 32 Docker pour les devs - 31 - Docker en CI/CD
  33. 33 Docker pour les devs - 32 - Au-dela de Compose
  34. 34 Docker pour les devs - 33 - Glossaire Docker de A a Z

25 - Builds multi-platform

Ce que tu vas apprendre

  • La différence entre ARM et x86 (et pourquoi Apple Silicon a change la donne)
  • Configurer docker buildx pour le multi-platform
  • Builder une image pour plusieurs architectures en une commande
  • Comment fonctionne l'emulation QEMU
  • Les performances natives vs emulees
  • Mettre en place des builds multi-platform en CI

Prerequisites

Avoir suivi l'article sur l'optimisation. Connaitre docker buildx. Avoir Docker Desktop ou Docker Engine recent.


Le jour ou j'ai achete un Mac M1, j'ai découvert un problème que je n'avais jamais eu : mes images Docker ne marchaient plus sur le serveur de prod. Mes images etaient buildees pour ARM (le processeur du Mac), mon serveur tourne sur x86. Le conteneur demarrait et crashait immédiatement avec un "exec format error". Bienvenue dans le monde du multi-platform.

ARM vs x86 : le contexte

Pendant 20 ans, le monde des serveurs etait simple : tout etait x86_64 (aussi appele amd64). Intel et AMD, meme jeu d'instructions, tout est compatible.

Puis Apple a sorti ses puces M1 (ARM), AWS a lance Graviton (ARM), et soudainement les développeurs buildent sur une architecture et deploient sur une autre. Ou sur les deux.

Les architectures courantes dans Docker :

  • linux/amd64 : Intel/AMD, la majorite des serveurs cloud
  • linux/arm64 : Apple Silicon, AWS Graviton, Raspberry Pi 4
  • linux/arm/v7 : Raspberry Pi 3, anciens appareils ARM

Quand tu fais docker pull nginx, Docker telecharge automatiquement la version qui correspond a ton architecture. Mais si l'image n'existe pas pour ton archi, ca ne marche pas.

docker buildx : le builder multi-platform

buildx est le builder etendu de Docker. Il remplace docker build et ajoute le support multi-platform :

bash# Verifier les builders disponibles
docker buildx ls

# Creer un builder multi-platform
docker buildx create --name multibuilder --use

# Verifier les plateformes supportees
docker buildx inspect --bootstrap

Le builder par défaut ne supporte que ta plateforme native. Le builder multibuilder utilise QEMU pour emuler les autres architectures.

Builder pour plusieurs plateformes

bash# Builder pour ARM et x86 en une commande
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t monregistry/monapp:latest \
  --push .

Le --push est nécessaire parce que les images multi-platform ne peuvent pas etre stockees localement (c'est une liste de manifestes qui pointe vers plusieurs images). Il faut les pousser vers un registry.

Pour tester localement sans registry :

bash# Builder pour une seule plateforme et charger localement
docker buildx build \
  --platform linux/amd64 \
  --load \
  -t monapp:latest .

# Ou exporter en fichier
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --output type=oci,dest=monapp.tar .

Le flag --platform dans le Dockerfile

Tu peux aussi spécifier la plateforme dans le Dockerfile pour les cas ou tu as besoin de mixer :

dockerfile# Le builder tourne sur ta plateforme native
FROM --platform=$BUILDPLATFORM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# L'image finale cible la plateforme demandee
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

Le $BUILDPLATFORM correspond a ta machine. Le stage builder tourne nativement (rapide), et seule l'image finale est construite pour la plateforme cible. Ca fonctionne bien pour les builds JavaScript ou le code est interprète, pas compile.

Pour les langages compiles (Go, Rust), il faut cross-compiler :

dockerfileFROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
ARG TARGETOS TARGETARCH
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o server .

FROM alpine:3.19
COPY --from=builder /app/server /server
CMD ["/server"]

Les variables $TARGETOS et $TARGETARCH sont injectees automatiquement par BuildKit. Go sait cross-compiler nativement, donc le build est rapide meme pour une autre architecture.

QEMU : l'emulation transparente

Quand tu builds une image ARM sur une machine x86 (ou inversement), Docker utilise QEMU pour emuler le processeur cible. C'est transparent : tu ne configures rien, les binaires QEMU sont enregistres dans le kernel.

bash# Installer les emulateurs QEMU (Docker Desktop les a deja)
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

# Verifier les architectures supportees
docker buildx inspect --bootstrap

Sur paltemps.fr, je build sur un Mac M1 (ARM) et je deploie sur un VPS x86. L'emulation QEMU me permet de tester l'image x86 localement avant de déployer.

Performances : natif vs emule

Le problème de l'emulation, c'est la vitesse. Un build emule est 5 a 20 fois plus lent qu'un build natif :

Opération Natif Emule Ralentissement
npm ci (500 deps) 30s 4min x8
go build 15s 2min x8
cargo build (Rust) 2min 30min+ x15
Python pip install 20s 3min x9

Pour les langages interprètes (Node.js, Python), c'est tolerable. Pour Rust, c'est inutilisable en emulation. La solution : cross-compiler quand le langage le permet, ou builder nativement sur chaque architecture.

Pour le dev quotidien, builde pour ta plateforme locale. Le multi-platform, c'est pour la CI et les releases.

CI multi-platform

Un workflow GitHub Actions qui build pour ARM et x86 :

yamlname: Build multi-platform
on:
  push:
    tags: ["v*"]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-qemu-action@v3

      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Le setup-qemu-action installe les emulateurs. Le setup-buildx-action configure le builder. Le cache GitHub Actions (type=gha) est gratuit et accéléré les builds suivants, comme on l'a vu dans l'article sur l'optimisation.

Pour aller plus vite en CI, certains utilisent des runners natifs pour chaque architecture :

yamljobs:
  build-amd64:
    runs-on: ubuntu-latest
    # build natif x86

  build-arm64:
    runs-on: ubuntu-latest-arm64  # runner ARM natif
    # build natif ARM

  manifest:
    needs: [build-amd64, build-arm64]
    runs-on: ubuntu-latest
    steps:
      - run: |
          docker manifest create ghcr.io/myapp:latest \
            ghcr.io/myapp:latest-amd64 \
            ghcr.io/myapp:latest-arm64
          docker manifest push ghcr.io/myapp:latest

Chaque architecture build nativement (rapide), puis un job final combine les deux dans un manifest. C'est plus complexe a maintenir mais nettement plus rapide pour les gros projets.

Manifest lists

Quand tu fais docker pull nginx, Docker recoit en fait une manifest list qui pointe vers plusieurs images :

bash# Voir le manifeste d'une image
docker manifest inspect nginx:alpine

# Le resultat montre les plateformes disponibles
# linux/amd64, linux/arm64, linux/arm/v7, etc.

Docker choisit automatiquement la bonne image pour ta plateforme. C'est pour ca que docker pull fonctionne partout sans spécifier l'architecture.

Résumé

  • Apple Silicon (ARM) et les serveurs x86 ont créé le besoin du multi-platform.
  • docker buildx build --platform linux/amd64,linux/arm64 créé des images pour les deux.
  • QEMU emule les architectures manquantes, mais c'est 5 a 20x plus lent.
  • $BUILDPLATFORM et $TARGETARCH permettent de cross-compiler dans le Dockerfile.
  • En CI, les GitHub Actions officielles Docker simplifient tout le setup.
  • Pour le dev, reste sur ta plateforme native. Le multi-platform c'est pour la release.

Article précédent : Docker 24 - Optimisation Article suivant : Docker 26 - Ressources

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.