Aller au contenu
Conteneurs & Orchestration medium

Multi-arch et manifests : une image pour amd64 et arm64

22 min de lecture

logo podman

Ce guide vous apprend à construire une seule image qui fonctionne sur plusieurs architectures (amd64, arm64, arm/v7). Avec le même podman pull, l'image appropriée est téléchargée automatiquement.

À la fin, vous saurez :

  • Créer un manifest list (index multi-arch)
  • Construire avec émulation (simple mais lent)
  • Construire avec podman farm (natif et rapide)
  • Pousser vers un registry avec un seul tag

Quatre entrées pour aller directement au bon endroit : la théorie du manifest list, les deux stratégies de construction, et le workflow des commandes podman manifest.

Le besoin : une image pour plusieurs architectures

Section intitulée « Le besoin : une image pour plusieurs architectures »

Une image de conteneur n'est pas portable d'une architecture processeur à l'autre : les binaires qu'elle embarque sont compilés pour un jeu d'instructions précis. Un même déploiement mélange pourtant souvent des serveurs amd64 et des machines arm64, et personne ne veut maintenir deux tags séparés que les équipes finiront par confondre.

Vous avez une image qui fonctionne sur votre machine de développement (probablement amd64). Mais :

  • Vos serveurs ARM en production ne peuvent pas l'exécuter
  • Vos Raspberry Pi ne peuvent pas l'exécuter
  • Vos Mac M1/M2 (arm64) ne peuvent pas l'exécuter nativement

Erreur typique :

Fenêtre de terminal
podman run myapp:1.0
# exec format error

Un manifest list (ou "fat manifest") est un index qui pointe vers plusieurs images, une par architecture :

Manifest list multi-architecture : index pointant vers images amd64, arm64, arm/v7

Quand vous exécutez podman pull myapp:1.0, Podman :

  1. Télécharge le manifest list
  2. Identifie l'architecture de votre machine
  3. Télécharge l'image correspondante

Résultat : le même tag fonctionne partout.

Le manifest list ne contient aucune couche d'image : c'est uniquement une table d'aiguillage qui associe une plateforme (architecture plus os) au digest de l'image correspondante. Sa spécification est normalisée par l'OCI sous le nom d'image index, ce qui garantit que Docker, containerd et Podman l'interprètent de la même façon.

Un manifest list est un fichier JSON stocké dans le registry :

{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:abc123...",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:def456...",
"platform": {
"architecture": "arm64",
"os": "linux"
}
}
]
}

Les architectures les plus fréquentes sont amd64 (serveurs, cloud) et arm64 (Raspberry Pi, Mac M1/M2, AWS Graviton). Les autres sont rares mais utiles pour des cas spécifiques (mainframes IBM, vieux matériel).

ArchitectureMachinesExemples
linux/amd64x86_64, Intel/AMDServeurs, cloud, laptops Linux/Windows
linux/arm64ARM 64-bitRaspberry Pi 4+, Mac M1/M2, AWS Graviton
linux/arm/v7ARM 32-bitRaspberry Pi 2/3 (mode 32-bit)
linux/arm/v6ARM vieuxRaspberry Pi Zero
linux/386x86 32-bitVieux serveurs
linux/s390xIBM ZMainframes
linux/ppc64leIBM PowerPower servers

Avant de reconstruire quoi que ce soit, regardez si l'image de base que vous utilisez est déjà multi-arch. Si la sortie ne contient qu'une seule entrée platform, aucun manifest list n'existe et votre build échouera sur les autres architectures.

Fenêtre de terminal
# Voir les architectures disponibles
podman manifest inspect docker.io/library/nginx:alpine
# OU avec skopeo (plus lisible)
skopeo inspect --raw docker://docker.io/library/nginx:alpine | jq

L'émulation permet de construire une image arm64 sur une machine amd64 (et vice versa). C'est la solution la plus simple, mais la plus lente.

Le noyau Linux sait rediriger l'exécution d'un binaire étranger vers un interpréteur, grâce au mécanisme binfmt_misc. Une fois qemu-user-static enregistré, lancer un binaire arm64 sur une machine amd64 déclenche automatiquement la traduction des instructions par QEMU, sans que la commande podman build ait à s'en préoccuper.

QEMU : émulation arm64 sur machine amd64

L'installation se résume à déposer les binaires QEMU et à enregistrer les handlers auprès du noyau. Sur macOS, la machine virtuelle de Podman embarque déjà tout le nécessaire, il n'y a rien à installer.

Fenêtre de terminal
# Installer qemu-user-static
sudo dnf install qemu-user-static
# Vérifier
ls /usr/bin/qemu-*-static

L'option --platform fixe l'architecture cible du build, indépendamment de celle de votre machine. Chaque build produit une image distincte qu'il faudra ensuite rassembler dans un manifest list.

Fenêtre de terminal
# Build pour arm64 (émulé sur machine amd64)
podman build --platform linux/arm64 -t myapp:arm64 .
# Build pour amd64 (natif sur machine amd64)
podman build --platform linux/amd64 -t myapp:amd64 .

L'émulation a un coût : QEMU traduit chaque instruction CPU en temps réel. Pour un npm install ou une compilation Go, le ralentissement est significatif. Privilégiez l'émulation pour les tests rapides et podman farm pour les builds de production.

Architecture cibleSur machine amd64Temps relatif
linux/amd64Natif
linux/arm64Émulé5-10×
linux/arm/v7Émulé5-10×

podman farm distribue les builds sur des machines distantes de chaque architecture. Les builds sont natifs, donc rapides.

Votre poste ne compile rien lui-même : il envoie le contexte de build à chaque machine de la farm, puis rassemble les résultats. Le travail lourd de compilation se fait sur une machine dont le processeur correspond réellement à l'architecture visée, donc sans traduction d'instructions.

Podman farm : builds natifs distribués sur machines amd64 et arm64

Pour chaque machine distante :

  1. Podman installé
  2. SSH accessible depuis la machine locale
  3. Podman socket activé :
Fenêtre de terminal
# Sur chaque machine distante
systemctl --user enable --now podman.socket

Une farm n'est rien d'autre qu'un regroupement nommé de connexions distantes déjà déclarées dans Podman. Il faut donc d'abord enregistrer chaque machine, puis les rassembler sous un nom commun.

  1. Ajouter les connexions Podman

    Fenêtre de terminal
    # Machine amd64
    podman system connection add amd64 ssh://user@amd64-server.local/run/user/1000/podman/podman.sock
    # Machine arm64
    podman system connection add arm64 ssh://user@arm64-server.local/run/user/1000/podman/podman.sock
    # Vérifier
    podman system connection list
  2. Créer la farm

    Fenêtre de terminal
    podman farm create my-farm amd64 arm64
    # Vérifier
    podman farm list
  3. Tester

    Fenêtre de terminal
    podman farm list
    Résultat
    Name Connections
    my-farm amd64, arm64

Le tag doit désigner un registry accessible depuis toutes les machines de la farm : chaque nœud pousse son image, et le manifest list est assemblé ensuite à partir de ces images distantes.

Fenêtre de terminal
podman farm build \
--farm my-farm \
--platforms linux/amd64,linux/arm64 \
-t registry.example.com/myapp:1.0 \
.

Ce qui se passe :

  1. Le Containerfile est envoyé aux machines distantes
  2. Chaque machine build pour son architecture (natif)
  3. Les images sont poussées vers le registry
  4. Un manifest list est créé avec les deux images

Les options principales contrôlent la farm utilisée, les architectures cibles, et si le build doit aussi s'exécuter localement.

OptionDescriptionExemple
--farmNom de la farm--farm my-farm
--platformsArchitectures cibleslinux/amd64,linux/arm64
-t, --tagTag de l'image-t registry/app:1.0
--localBuild aussi sur la machine locale--local

Si vous avez déjà des images construites (par émulation ou farm), vous pouvez créer manuellement un manifest list.

Les sous-commandes suivent un workflow séquentiel : créer d'abord le manifest vide, puis ajouter les images une par une, puis pousser le tout vers un registry.

Sous-commandeAction
manifest createCréer un nouveau manifest list
manifest addAjouter une image au manifest
manifest inspectVoir le contenu du manifest
manifest pushPousser le manifest vers un registry
manifest rmSupprimer un manifest local

Les cinq étapes suivantes reproduisent à la main ce que podman farm build automatise. Notez le préfixe containers-storage: à l'étape d'ajout : sans lui, Podman cherche l'image dans un registry distant au lieu du stockage local.

  1. Construire les images pour chaque architecture

    Fenêtre de terminal
    # Sur machine amd64
    podman build --platform linux/amd64 -t myapp:amd64 .
    # Sur machine arm64 (ou émulé)
    podman build --platform linux/arm64 -t myapp:arm64 .
  2. Créer le manifest list

    Fenêtre de terminal
    podman manifest create myapp:1.0
  3. Ajouter les images

    Fenêtre de terminal
    # Pour des images locales, préfixer avec containers-storage:
    podman manifest add myapp:1.0 containers-storage:localhost/myapp:amd64
    podman manifest add myapp:1.0 containers-storage:localhost/myapp:arm64
  4. Vérifier

    Fenêtre de terminal
    podman manifest inspect myapp:1.0
    Résultat (simplifié)
    {
    "manifests": [
    {
    "platform": {"architecture": "amd64", "os": "linux"},
    "digest": "sha256:abc123..."
    },
    {
    "platform": {"architecture": "arm64", "os": "linux"},
    "digest": "sha256:def456..."
    }
    ]
    }
  5. Pousser vers le registry

    Fenêtre de terminal
    podman manifest push myapp:1.0 registry.example.com/myapp:1.0

Après le push, vérifiez que le manifest list est bien multi-arch :

Fenêtre de terminal
# Inspecter depuis le registry
podman manifest inspect registry.example.com/myapp:1.0
# Ou avec skopeo
skopeo inspect --raw docker://registry.example.com/myapp:1.0 | jq '.manifests[].platform'
Résultat
{"architecture": "amd64", "os": "linux"}
{"architecture": "arm64", "os": "linux"}

Le test décisif consiste à tirer le même tag depuis deux machines différentes et à comparer l'architecture réellement obtenue. Si les deux renvoient la même valeur, le manifest list n'a pas été poussé correctement.

Fenêtre de terminal
# Sur machine amd64
podman pull registry.example.com/myapp:1.0
podman inspect myapp:1.0 --format '{{.Architecture}}'
# amd64
# Sur machine arm64
podman pull registry.example.com/myapp:1.0
podman inspect myapp:1.0 --format '{{.Architecture}}'
# arm64

Objectif : construire une API Go pour amd64 et arm64.

L'application affiche l'architecture sur laquelle elle tourne, ce qui rend le résultat vérifiable d'un coup d'œil. Le Containerfile compile en statique (CGO_ENABLED=0) parce que l'image finale part de scratch, qui ne contient aucune bibliothèque système.

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)
})
http.ListenAndServe(":8080", nil)
}
Containerfile
# Build stage
FROM docker.io/library/golang:1.22-alpine AS builder
WORKDIR /app
COPY main.go .
# Build statique pour l'architecture cible
RUN CGO_ENABLED=0 go build -o /app/server main.go
# Runtime stage
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

Cette voie ne demande qu'une seule machine. Comptez plusieurs minutes sur le build arm64, la compilation Go étant intégralement traduite par QEMU.

  1. Construire les deux architectures

    Fenêtre de terminal
    podman build --platform linux/amd64 -t myapi:amd64 .
    podman build --platform linux/arm64 -t myapi:arm64 .
  2. Créer le manifest

    Fenêtre de terminal
    podman manifest create myapi:1.0
    podman manifest add myapi:1.0 containers-storage:localhost/myapi:amd64
    podman manifest add myapi:1.0 containers-storage:localhost/myapi:arm64
  3. Vérifier

    Fenêtre de terminal
    podman manifest inspect myapi:1.0
  4. Pousser

    Fenêtre de terminal
    podman manifest push myapi:1.0 registry.example.com/myapi:1.0

Une seule commande remplace les quatre étapes précédentes : la construction, la publication des images et l'assemblage du manifest list sont enchaînés automatiquement.

Fenêtre de terminal
# Prérequis : farm configurée avec connexions amd64 et arm64
podman farm build \
--farm my-farm \
--platforms linux/amd64,linux/arm64 \
-t registry.example.com/myapi:1.0 \
.

Trois contrôles suffisent : le manifest liste bien deux plateformes, le conteneur démarre sur votre architecture, et l'option --platform permet de forcer l'autre variante pour la tester sans changer de machine.

Fenêtre de terminal
# Inspecter le manifest list
podman manifest inspect registry.example.com/myapi:1.0
# Tester sur votre machine
podman run --rm registry.example.com/myapi:1.0
# Hello from linux/<votre-arch>
# Forcer une architecture différente (émulé)
podman run --rm --platform linux/arm64 registry.example.com/myapi:1.0
# Hello from linux/arm64

Les incidents multi-arch se manifestent rarement au moment du build : ils apparaissent au démarrage du conteneur, sur la machine cible, avec un message laconique. Les sections suivantes vont du symptôme le plus fréquent (exec format error) aux commandes qui permettent de confirmer l'état réel de l'émulation et de la farm.

La plupart des erreurs viennent de trois sources : l'absence de QEMU pour l'émulation, une image manquante dans le manifest, ou un registry qui ne supporte pas les manifest lists OCI.

ErreurCauseSolution
exec format errorArchitecture incompatibleVérifier --platform ou installer qemu
no suitable imageManifest sans cette archAjouter l'image pour cette arch
failed to push manifestRegistry ne supporte pas OCI indexUtiliser Docker Hub, GitHub, etc.
Farm build timeoutMachine distante inaccessibleVérifier SSH et podman socket

Ces quatre commandes répondent aux quatre questions utiles : sur quelle architecture suis-je, l'émulation est-elle enregistrée dans le noyau, que contient réellement mon manifest, et les machines de la farm répondent-elles.

Fenêtre de terminal
# Architecture de la machine locale
podman info --format '{{.Host.Arch}}'
# Vérifier l'émulation disponible
ls /proc/sys/fs/binfmt_misc/
# Voir les architectures d'un manifest
podman manifest inspect myapp:1.0 | jq '.manifests[].platform'
# Vérifier les connexions de la farm
podman system connection list
podman farm list

Les enregistrements binfmt_misc ne survivent pas toujours à un redémarrage selon la distribution : c'est la cause la plus fréquente d'une émulation qui marchait hier et plus aujourd'hui. Le test final doit renvoyer aarch64 et non l'architecture de votre machine.

Fenêtre de terminal
# Vérifier que qemu-user-static est installé
ls /usr/bin/qemu-*-static
# Réenregistrer les handlers (Linux)
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# Test rapide
podman run --rm --platform linux/arm64 alpine:3.21 uname -m
# aarch64

Le multi-arch coûte du temps de build et de la complexité d'outillage : la première décision est donc de choisir la stratégie adaptée à votre volume, puis d'écrire un Containerfile qui ne présuppose aucune architecture. Les recommandations ci-dessous couvrent ces deux points, plus la gestion des tags une fois le manifest publié.

Le choix dépend de votre contexte : l'émulation est gratuite mais lente, farm est rapide mais nécessite des machines de chaque architecture. Pour la plupart des projets open source, l'émulation suffit.

SituationRecommandation
Dev local, tests rapidesÉmulation
CI/CD rapideFarm
Projet open sourceÉmulation (pas de machines dédiées)
Production à grande échelleFarm

Un Containerfile qui copie un binaire déjà compilé condamne l'image à une seule architecture. Pour les langages compilés, la cross-compilation évite l'émulation : Podman renseigne automatiquement l'argument TARGETARCH avec la plateforme demandée, il suffit de le transmettre au compilateur.

  1. Containerfile compatible : éviter les dépendances architecture-spécifiques

    # ✅ Bon : dépendances universelles
    FROM alpine:3.21
    RUN apk add --no-cache ca-certificates
    # ❌ Mauvais : binaire compilé pour une seule arch
    COPY mybinary /app/mybinary
  2. Multi-stage avec cross-compilation (Go, Rust) :

    FROM golang:1.22 AS builder
    ARG TARGETARCH # Variable automatique : arm64 ou amd64
    RUN GOARCH=${TARGETARCH} go build -o /app
  3. Tester sur toutes les architectures avant de pousser

Un même manifest list peut être poussé sous plusieurs tags, du plus précis au plus large. Les consommateurs choisissent alors leur niveau de stabilité : 1.0.0 ne bougera jamais, latest suit la dernière publication.

Fenêtre de terminal
# Manifest list avec tag sémantique
podman manifest push myapi:1.0 registry/myapi:1.0.0
podman manifest push myapi:1.0 registry/myapi:1.0
podman manifest push myapi:1.0 registry/myapi:1
podman manifest push myapi:1.0 registry/myapi:latest
  1. Manifest list = index pointant vers plusieurs images (une par arch)
  2. Émulation (QEMU) : simple, lente, pas de machine distante
  3. Farm : builds natifs distribués, rapide, nécessite des machines
  4. Workflow : manifest createaddinspectpush
  5. Vérification : manifest inspect pour voir les architectures
  6. Test : --platform pour forcer une architecture (émulée)

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn