Une image multi-architecture est un index : un tag unique derrière lequel Docker choisit la variante qui correspond à la machine (amd64 sur un PC, arm64 sur un serveur ARM). Cette leçon construit un tel index avec Docker Buildx, le publie dans un registre local, puis exécute chaque variante pour vérifier que le programme qu'elle contient est bien celui de sa plateforme.
Un build qui réussit ne prouve pas que l'image fonctionne : une variante arm64 peut très bien contenir un binaire amd64, et seul un lancement le montre. Vous apprendrez aussi à reconnaître l'erreur exec format error, à choisir entre émulation et compilation croisée, et à déclarer ce que vous n'avez pas rejoué. Toutes les mesures viennent de Docker Engine 29.9.2, Buildx 0.38.1 et BuildKit v0.34.0, avec le magasin d'images containerd, sur une VM Debian 13 amd64.
Le cache distribué entre exécutions de CI est traité dans Cache BuildKit en CI et promotion d'image entre registres. Cette leçon enregistre l'émulateur QEMU à la main, sur la VM de mesure ; son enregistrement sur un runner de CI n'a pas été mesuré.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Lire les plateformes d'un builder et créer un builder
docker-container, puis le retirer - Reconnaître
exec format erroret enregistrer l'émulation QEMU - Construire deux plateformes en émulation puis en compilation croisée, et comparer les durées
- Repérer le piège d'un binaire de la mauvaise architecture dans un index pourtant correct
- Lire un index publié sans compter ses entrées, et exécuter chaque variante par son digest
- Prévoir ce que fait
docker run --platformselon ce qui est déjà en local - Déclarer ce qui n'a pas été rejoué sur du matériel réel
Prérequis
Section intitulée « Prérequis »- Avoir lu BuildKit : secrets de build et accès privé et Séparer construction et exécution
- Savoir lire un tag et un digest (Lire une image OCI et son identité)
- Un Docker 29.9.2 fonctionnel (installation de Docker), le droit de lancer un conteneur
--privileged(la section sur l'émulation en a besoin), et les commandescurl,fileetpython3
Quel builder et quelles plateformes avez-vous sans rien configurer ?
Section intitulée « Quel builder et quelles plateformes avez-vous sans rien configurer ? »Un builder est le composant qui exécute vos constructions : il fait tourner BuildKit, le moteur de construction de Docker, et son pilote (driver) dit où BuildKit s'exécute. Le builder utilisé par défaut s'appelle default et fonctionne avec le pilote docker : BuildKit est alors intégré au moteur.
Vérifiez d'abord le magasin d'images, c'est-à-dire l'endroit où Docker range les images et leurs couches, car la construction multi-plateforme en dépend.
docker info | grep -Ei "storage|snapshotter|Server Version|Architecture" Server Version: 29.9.2 Storage Driver: overlayfs driver-type: io.containerd.snapshotter.v1 Architecture: x86_64La ligne driver-type: io.containerd.snapshotter.v1 indique le magasin containerd, celui que Docker choisit seul sur une nouvelle installation de Docker Engine 29 ou plus (une mise à jour garde l'ancien magasin ; docker info dit lequel vous utilisez). C'est lui qui sait garder, côte à côte, plusieurs plateformes sous un même tag. Les mesures de cette leçon ne valent pas pour l'ancien magasin classique, que la leçon n'a pas testé. Listez maintenant les builders.
docker buildx lsNAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMSdefault* docker \_ default \_ default running v0.34.0 linux/amd64 (+3)L'étoile marque le builder actif. La colonne PLATFORMS est abrégée : (+3) signale trois plateformes de plus. La commande docker buildx inspect --bootstrap donne la liste complète ; la sortie ci-dessous est réduite à ses premières lignes.
docker buildx inspect --bootstrapName: defaultDriver: docker
Nodes:Name: defaultEndpoint: defaultStatus: runningBuildKit version: v0.34.0Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3Sans rien configurer, le builder ne connaît que les variantes de votre processeur. Pour exécuter des instructions arm64 pendant une construction, il manque un émulateur, que la section sur l'émulation ajoute.
Que change un builder docker-container, et comment le retirer ?
Section intitulée « Que change un builder docker-container, et comment le retirer ? »Un builder docker-container lance BuildKit dans un conteneur à part, au lieu d'utiliser celui de Docker : vous le créez pour disposer d'un BuildKit distinct. Créez un builder nommé multi avec une image BuildKit épinglée par version et par digest, et démarrez-le.
docker buildx create --name multi --driver docker-container \ --driver-opt image=moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c --usedocker buildx inspect --bootstrap multimulti#1 [internal] booting buildkit#1 pulling image moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c#1 pulling image moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c 9.1s done#1 creating container buildx_buildkit_multi0#1 creating container buildx_buildkit_multi0 0.9s done#1 DONE 10.0sName: multiDriver: docker-container[...]Nodes:Name: multi0Endpoint: unix:///var/run/docker.sockDriver Options: image="moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c"Status: runningBuildKit daemon flags: --allow-insecure-entitlement=network.hostBuildKit version: v0.34.0Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386[...]Lisez trois choses dans la sortie de buildx inspect. Docker tire l'image BuildKit épinglée, puis crée le conteneur buildx_buildkit_multi0. Avec cette image, la ligne BuildKit version doit afficher v0.34.0, la même version que le builder par défaut. Sans l'option --driver-opt image=…, Buildx tire moby/buildkit:buildx-stable-1, un tag mobile : lors de la mesure de la leçon, il donnait BuildKit v0.33.1, quand le builder par défaut exécutait v0.34.0. Enfin, la ligne Platforms liste ce que le builder gère : avec l'image épinglée, ce sont les plateformes de votre processeur et linux/386, sans arm64.
La documentation Docker sur le multi-plateforme indique que les images officielles de BuildKit embarquent des émulateurs QEMU en mode utilisateur. La leçon n'a pas vérifié que ces émulateurs servent : la liste Platforms ci-dessus ne contenait pas arm64, et c'est elle qui fait foi. Toutes les mesures d'émulation de la leçon portent sur le builder par défaut de Docker, avec l'émulation enregistrée par binfmt ; seule la publication vers un registre local, plus bas, passe par un builder docker-container.
Un builder docker-container garde ses résultats dans son propre conteneur. Pour retrouver une image dans docker image ls, il faut la demander avec --load ; --push l'envoie au registre sans rien charger en local. Une autre différence, le réseau, apparaît quand on publie vers un registre local ; la section sur l'index publié la traite. Revenez au builder par défaut, retirez multi, puis vérifiez qu'il ne reste ni builder ni conteneur.
docker buildx use defaultdocker buildx rm multidocker buildx lsdocker ps -a --format "{{.Names}} {{.Image}}"multi removedNAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMSdefault* docker \_ default \_ default running v0.34.0 linux/amd64 (+3)Le message multi removed confirme le retrait, et docker ps -a n'affiche aucune ligne buildx_buildkit_multi0 : le conteneur du builder a disparu avec lui. L'image BuildKit reste en revanche dans docker image ls ; la section de nettoyage la supprime.
Pourquoi un programme arm64 échoue-t-il avec exec format error ?
Section intitulée « Pourquoi un programme arm64 échoue-t-il avec exec format error ? »Un processeur amd64 ne sait pas exécuter un programme compilé pour arm64. Docker peut tirer l'image arm64 sans difficulté, car télécharger des fichiers n'exige aucune exécution. Le test se fait avec Alpine 3.24.3, épinglée par digest.
docker pull --platform linux/arm64 alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9adocker run --rm --platform linux/arm64 alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a uname -mecho "code=$?"docker.io/library/alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a: Pulling from library/alpined3531b7a4f21: Pulling fs layerd3531b7a4f21: Download complete2a02c517eb64: Download complete4f276f238bfa: Download completed3531b7a4f21: Pull completeDigest: sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aStatus: Image is up to date for alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9adocker.io/library/alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aexec /bin/uname: exec format errorcode=255Le tirage réussit (sur la machine de mesure, l'index d'Alpine était déjà présent, d'où la ligne Image is up to date : elle ne signale pas une absence de variante arm64). Le lancement échoue avec exec format error et le code 255 : le noyau lit un fichier d'une autre architecture et ne sait pas l'exécuter. La même erreur apparaît dans une construction dès qu'une instruction RUN doit exécuter un programme de la plateforme cible. Voici le Dockerfile de test, qui ne contient qu'un RUN.
mkdir -p ~/multiarch && cd ~/multiarchFROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aRUN uname -m > /archCMD ["cat", "/arch"]docker buildx build --platform linux/arm64 -f Dockerfile.run -t demo/plateformes:run-arm64 --load .echo "build-exit=$?"#5 [2/2] RUN uname -m > /arch#5 0.186 exec /bin/sh: exec format error#5 ERROR: process "/bin/sh -c uname -m > /arch" did not complete successfully: exit code: 255[...]ERROR: failed to build: failed to solve: process "/bin/sh -c uname -m > /arch" did not complete successfully: exit code: 255build-exit=1La sortie est abrégée. L'instruction FROM de l'étape précédente s'est terminée sans erreur : charger et extraire les couches d'une image arm64 n'exécute rien. C'est le RUN, qui lance /bin/sh de l'image, qui échoue. Une instruction COPY, qui recopie seulement des fichiers, n'exécute aucun programme non plus. Retenez la règle : seuls les RUN exigent l'émulation.
Comment enregistrer l'émulation QEMU, et comment la retirer ?
Section intitulée « Comment enregistrer l'émulation QEMU, et comment la retirer ? »L'émulation consiste à faire exécuter un programme arm64 par QEMU, un logiciel qui imite ce processeur. Elle passe par binfmt_misc, un mécanisme du noyau Linux qui associe un format de fichier à l'interpréteur chargé de le lancer. Si QEMU est enregistré pour arm64, le noyau lui confie les binaires arm64 et le conteneur s'exécute. Regardez d'abord ce qui est enregistré sur la machine.
ls /proc/sys/fs/binfmt_miscpython3.13registerstatusIl n'y a aucune entrée qemu-* : seul l'interpréteur Python est déclaré. Notez la liste que vous obtenez chez vous : le nettoyage en fin de leçon s'y réfère pour ne pas retirer une émulation qui existait déjà.
L'image tonistiigi/binfmt enregistre les émulateurs. Son tag latest se déplace à chaque publication : la leçon épingle donc le tag de version qemu-v10.2.3-68 et son digest. Lancée sans option, elle ne modifie rien et liste ce que le noyau accepte.
docker run --privileged --rm tonistiigi/binfmt:qemu-v10.2.3-68@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0{ "supported": [ "linux/amd64", "linux/amd64/v2", "linux/amd64/v3", "linux/386" ], "emulators": [ "python3.13" ]}Au premier lancement, Docker tire d'abord l'image : des lignes Pulling from tonistiigi/binfmt précèdent le JSON, non reproduites ici. L'option --privileged est nécessaire parce que ce conteneur écrit dans la configuration du noyau de l'hôte, partagée par tous les conteneurs. C'est aussi pourquoi l'image est épinglée par digest : un conteneur privilégié ne doit pas pouvoir changer à votre insu. Enregistrez maintenant l'émulateur arm64.
docker run --privileged --rm tonistiigi/binfmt:qemu-v10.2.3-68@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 --install arm64installing: arm64 OK{ "supported": [ "linux/amd64", "linux/amd64/v2", "linux/amd64/v3", "linux/arm64", "linux/386" ], "emulators": [ "python3.13", "qemu-aarch64" ]}La plateforme linux/arm64 apparaît dans supported et qemu-aarch64 dans emulators. Le noyau possède désormais une entrée que vous pouvez lire.
cat /proc/sys/fs/binfmt_misc/qemu-aarch64enabledinterpreter /usr/bin/qemu-aarch64flags: POCFoffset 0magic 7f454c460201010000000000000000000200b700mask ffffffffffffff00fffffffffffffffffeffffffLe champ magic est la signature d'un exécutable arm64 au format ELF (le format des exécutables Linux), et interpreter désigne l'émulateur chargé de le lancer. Relancez la commande qui échouait, puis la construction.
docker run --rm --platform linux/arm64 alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a uname -mcd ~/multiarch && docker buildx build --platform linux/arm64 -f Dockerfile.run -t demo/plateformes:run-arm64 --load .docker run --rm demo/plateformes:run-arm64aarch64WARNING: The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64/v3) and no specific platform was requestedaarch64La première ligne, aarch64, est la réponse du conteneur arm64 (aarch64 est le nom que uname donne à cette architecture) : l'erreur 255 a disparu. La construction affiche sa progression, non reproduite ici, et dure moins d'une seconde sur la machine de mesure ; docker run répond ensuite aarch64, précédé d'un avertissement.
Cet avertissement ne signale pas une panne : le tag désigne une image arm64 sur une machine amd64, et Docker le dit parce que vous n'avez pas précisé --platform.
Pour retirer l'émulation, passez --uninstall avec le nom de l'entrée du noyau.
docker run --privileged --rm tonistiigi/binfmt:qemu-v10.2.3-68@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 --uninstall qemu-aarch64uninstalling: qemu-aarch64 OK{ "supported": [ "linux/amd64", "linux/amd64/v2", "linux/amd64/v3", "linux/386" ], "emulators": [ "python3.13" ]}La liste revient à l'état initial. Réenregistrez l'émulation avec la même commande --install arm64, et gardez-la jusqu'à la section de nettoyage, qui la retire.
docker run --privileged --rm tonistiigi/binfmt:qemu-v10.2.3-68@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 --install arm64La sortie est celle de la première installation : installing: arm64 OK, puis la liste où figurent linux/arm64 et qemu-aarch64.
Comment construire une image pour deux plateformes ?
Section intitulée « Comment construire une image pour deux plateformes ? »Enregistrez chaque fichier de cette leçon dans ~/multiarch, sous le nom indiqué en titre du bloc. L'option --platform accepte une liste, et --load rapatrie le résultat dans le magasin local. Avec le magasin containerd, ces deux options fonctionnent ensemble avec le builder par défaut, sans builder supplémentaire. Le programme d'essai est un petit binaire Go qui affiche l'architecture pour laquelle il a été compilé, et un mot écrit à l'envers pour qu'il soit reconnaissable.
package main
import ( "fmt" "runtime")
func reverse(s string) string { r := []rune(s) for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } return string(r)}
func main() { fmt.Printf("GOOS=%s GOARCH=%s app=%s\n", runtime.GOOS, runtime.GOARCH, reverse("plateformes"))}module demo/plateformes
go 1.27Le premier Dockerfile compile dans l'image golang de la plateforme cible : sous arm64, le compilateur lui-même est un programme arm64, donc émulé. L'image Go est épinglée par le digest de son index.
FROM golang:1.27-alpine@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3deb AS buildWORKDIR /srcCOPY go.mod main.go ./RUN CGO_ENABLED=0 go build -o /out/app .
FROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aCOPY --from=build /out/app /appENTRYPOINT ["/app"]Tirez d'abord l'image Go pour les deux plateformes, afin que le téléchargement ne compte pas dans la durée. L'option --no-cache force ensuite la compilation à s'exécuter : sans elle, un cache déjà rempli fausserait la mesure.
docker pull --platform linux/amd64 golang:1.27-alpine@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3deb | tail -2docker pull --platform linux/arm64 golang:1.27-alpine@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3deb | tail -2time docker buildx build --no-cache --platform linux/amd64,linux/arm64 -f Dockerfile.emul -t demo/plateformes:emul --load --progress=plain . 2>&1 | tail -25[...]#18 [linux/amd64 stage-1 2/2] COPY --from=build /out/app /app#18 DONE 0.1s
#17 [linux/arm64 build 4/4] RUN CGO_ENABLED=0 go build -o /out/app .#17 DONE 51.2s
#19 [linux/arm64 stage-1 2/2] COPY --from=build /out/app /app#19 DONE 0.1s
#20 exporting to image#20 exporting layers#20 exporting layers 0.3s done#20 exporting manifest sha256:47eabc785aa6062c7586bf0d0f1ce418872bf936d76cd9845dccb68c86a87b2c#20 exporting manifest sha256:47eabc785aa6062c7586bf0d0f1ce418872bf936d76cd9845dccb68c86a87b2c 0.0s done#20 exporting config sha256:48f1a928b8e92d4358ac4f7cb83b06848580009f8fef7f0a3c83f0221e9a80b6 0.0s done#20 exporting attestation manifest sha256:b08516758cca3cb0805adf6cc8efec81d790cdad081267fd98aca3f1f7e7e478 0.0s done#20 exporting manifest sha256:ff6c1d79dadae09bd2037f3bb02364d335125f93b84587c6eb63251f1049b269 0.0s done#20 exporting config sha256:06478064ef7e28675e51f48ddb7ab6e6b9e9c4dc3f347533d83dcc41db4bcac4 0.0s done#20 exporting attestation manifest sha256:f2fd51c8dcefeeb8713fb17b2670b1f7891dc8c001d5270cb29d97928f7d1b10 0.0s done#20 exporting manifest list sha256:870f4914b947c2e5656e723ab9eb4d416ff2fc2e30c695c0b07220bd981cccc4#20 exporting manifest list sha256:870f4914b947c2e5656e723ab9eb4d416ff2fc2e30c695c0b07220bd981cccc4 0.0s done#20 naming to docker.io/demo/plateformes:emul done#20 unpacking to docker.io/demo/plateformes:emul 0.0s done#20 DONE 0.6s
real 0m56.805suser 0m0.097ssys 0m0.027sCherchez deux repères. L'étape [linux/arm64 build 4/4], la compilation sous QEMU, dure 51,2 s ; la construction entière dure 56,8 s (ligne real).
Les lignes exporting montrent ce que produit une seule construction. Un manifeste est la fiche d'une variante : il liste ses couches et sa configuration. La construction en écrit un par plateforme, puis un index, que Docker appelle aussi liste de manifestes (manifest list), qui les réunit sous un seul tag. Les lignes attestation manifest sont expliquées avec l'index publié, dans la section qui lui est consacrée. Lisez l'index avec --tree.
docker image ls --treeIMAGE ID DISK USAGE CONTENT SIZE EXTRAdemo/plateformes:emul 870f4914b947 22.3MB 10.8MB├─ linux/amd64 47eabc785aa6 16.8MB 5.3MB└─ linux/arm64 ff6c1d79dada 5.54MB 5.54MBL'option --tree est marquée expérimentale dans le client Docker 29.9.2 : son affichage peut changer. La sortie est limitée à l'image de ce test : la liste complète contient aussi les images de base. La ligne du dessus est l'index, les deux lignes du dessous sont les variantes. Le champ ID d'une variante est l'identifiant de son manifeste. Pour examiner l'une d'elles, ajoutez --platform à docker image inspect, puis lancez-la avec la même option.
docker image inspect --platform linux/arm64 --format '{{.Os}}/{{.Architecture}} id={{.Id}}' demo/plateformes:emuldocker run --rm --platform linux/arm64 demo/plateformes:emuldocker image inspect --platform linux/amd64 --format '{{.Os}}/{{.Architecture}} id={{.Id}}' demo/plateformes:emuldocker run --rm --platform linux/amd64 demo/plateformes:emullinux/arm64 id=sha256:ff6c1d79dadae09bd2037f3bb02364d335125f93b84587c6eb63251f1049b269GOOS=linux GOARCH=arm64 app=semrofetalplinux/amd64 id=sha256:47eabc785aa6062c7586bf0d0f1ce418872bf936d76cd9845dccb68c86a87b2cGOOS=linux GOARCH=amd64 app=semrofetalpChaque variante répond avec son architecture : GOARCH=arm64 pour l'une, GOARCH=amd64 pour l'autre. C'est le test à retenir, car le programme rapporte ce pour quoi il a été compilé, pas ce que docker image inspect déclare dans le manifeste.
Faut-il émuler ou compiler en croisé ?
Section intitulée « Faut-il émuler ou compiler en croisé ? »Compiler en émulation fonctionne avec n'importe quel Dockerfile, mais le compilateur tourne lentement sous QEMU. La compilation croisée évite cette lenteur : le compilateur tourne en natif sur votre machine, et il produit un binaire pour une autre architecture. Go le permet avec deux variables, GOOS et GOARCH. Cette section compare les deux méthodes, puis montre comment la seconde se trompe.
Le schéma met les deux chemins en regard. Dans les deux, l'image finale ne fait qu'un COPY, et seul change l'endroit où tourne le compilateur. L'encadré mis en évidence est l'étape qui passe par QEMU.
Comment compiler en croisé avec TARGETARCH ?
Section intitulée « Comment compiler en croisé avec TARGETARCH ? »BuildKit fournit à chaque plateforme des variables de construction. BUILDPLATFORM est la plateforme de la machine qui construit, TARGETOS et TARGETARCH celles de l'image visée. Elles ne sont disponibles dans une étape qu'une fois déclarées par ARG. L'instruction FROM --platform=$BUILDPLATFORM demande l'image du compilateur pour la machine qui construit, et non pour la cible.
FROM --platform=$BUILDPLATFORM golang:1.27-alpine@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3deb AS buildARG TARGETOSARG TARGETARCHWORKDIR /srcCOPY go.mod main.go ./RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/app .
FROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aCOPY --from=build /out/app /appENTRYPOINT ["/app"]L'étape finale ne contient qu'un COPY : elle n'exécute aucun programme, donc elle n'a pas besoin d'émulation, même pour arm64. Mesurez la durée dans les mêmes conditions, avec --no-cache.
time docker buildx build --no-cache --platform linux/amd64,linux/arm64 -f Dockerfile -t demo/plateformes:cross --load --progress=plain . 2>&1 | tail -28[...]#12 [linux/amd64->arm64 build 4/4] RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o /out/app .#12 DONE 7.4s
#13 [linux/amd64 build 4/4] RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/app .#13 DONE 7.4s
#14 [linux/arm64 stage-1 2/2] COPY --from=build /out/app /app#14 DONE 0.1s
#15 [linux/amd64 stage-1 2/2] COPY --from=build /out/app /app#15 DONE 0.1s
#16 exporting to image#16 exporting layers#16 exporting layers 0.3s done#16 exporting manifest sha256:25ae59cb20b1fb92a8e54f6581127b2a477814c6ecb923e1dec3bb53a10c46e6#16 exporting manifest sha256:25ae59cb20b1fb92a8e54f6581127b2a477814c6ecb923e1dec3bb53a10c46e6 0.0s done#16 exporting config sha256:2214fa5559caa4831b35d8c492b0e5b4ecda73153c84278b5b3c9d85d6a85439 0.0s done#16 exporting attestation manifest sha256:1f0afc1a68003ff2ce02582b366c9326c3e476aaabcdaa830d3a86a57d95bbd9#16 exporting attestation manifest sha256:1f0afc1a68003ff2ce02582b366c9326c3e476aaabcdaa830d3a86a57d95bbd9 0.0s done#16 exporting manifest sha256:ed2301bb69730e8ae287004665f0db8f615f06aa99fc9146675104ed25bda0fd 0.0s done#16 exporting config sha256:883c4afb1ea39ce5086851996f7623f65b85a45a033831a84cb43f5c3d445f28 0.0s done#16 exporting attestation manifest sha256:28a4fe35311bb6f077576a1173d4a8d8d3d99da2294e6979ae78a1752206b200 0.1s done#16 exporting manifest list sha256:7d0d83dd21f32f33fd5990d2877007719464fd75331d76d38f62e27e40b7be26#16 exporting manifest list sha256:7d0d83dd21f32f33fd5990d2877007719464fd75331d76d38f62e27e40b7be26 0.0s done#16 naming to docker.io/demo/plateformes:cross done#16 unpacking to docker.io/demo/plateformes:cross 0.0s done#16 DONE 0.6s
real 0m8.883suser 0m0.044ssys 0m0.035sL'étape de compilation s'appelle maintenant [linux/amd64->arm64 build 4/4] : la flèche indique qu'elle s'exécute sur amd64 pour viser arm64. Elle dure 7,4 s et la construction entière 8,9 s. La sortie montre aussi la compilation pour amd64, [linux/amd64 build 4/4], sans flèche puisqu'elle vise la machine elle-même : 7,4 s également.
| Méthode | Compilation arm64 | Construction des deux plateformes |
|---|---|---|
Émulée (Dockerfile.emul) | 51,2 s | 56,8 s |
Croisée (Dockerfile) | 7,4 s | 8,9 s |
Ces durées viennent d'une seule exécution sur la VM Debian 13 amd64 de la mesure, chaque construction étant lancée avec --no-cache : lisez-les comme un ordre de grandeur et mesurez les vôtres. Pour savoir si les deux méthodes donnent le même programme, copiez les binaires hors des images et comparez leurs empreintes.
mkdir -p out-cross-arm64 && docker create --platform linux/arm64 --name x-cross-arm64 demo/plateformes:cross >/dev/null && docker cp x-cross-arm64:/app out-cross-arm64/app && docker rm x-cross-arm64 >/dev/null && file out-cross-arm64/app && sha256sum out-cross-arm64/appmkdir -p out-emul-arm64 && docker create --platform linux/arm64 --name x-emul-arm64 demo/plateformes:emul >/dev/null && docker cp x-emul-arm64:/app out-emul-arm64/app && docker rm x-emul-arm64 >/dev/null && file out-emul-arm64/app && sha256sum out-emul-arm64/appout-cross-arm64/app: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, Go BuildID=fRa4Z91i4F52WwsekNq2/BXCI_4KDnM1hvHaxGZBI/nrGeYniJwRRWDvAWaSZ4/q3m2sBdQv68dkukXKbLW, BuildID[sha1]=4df9c0d5dcf12b43ebc850eb2dea127d64d7fc9f, with debug_info, not strippedc0947d04b45aca0bd756ad70b45fad837d8a40e87c55343f769dd4fff618624d out-cross-arm64/appout-emul-arm64/app: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, Go BuildID=fRa4Z91i4F52WwsekNq2/BXCI_4KDnM1hvHaxGZBI/nrGeYniJwRRWDvAWaSZ4/q3m2sBdQv68dkukXKbLW, BuildID[sha1]=4df9c0d5dcf12b43ebc850eb2dea127d64d7fc9f, with debug_info, not strippedc0947d04b45aca0bd756ad70b45fad837d8a40e87c55343f769dd4fff618624d out-emul-arm64/appLa commande file lit l'en-tête du fichier : ARM aarch64 confirme l'architecture sans exécuter le programme. Les deux empreintes sont identiques (c0947d04…) : sur ce programme, la compilation émulée et la compilation croisée donnent le même binaire, octet pour octet. La compilation croisée ne dégrade donc pas le résultat ici ; elle ne dit rien d'un programme qui dépendrait de bibliothèques C ou dont le RUN final exécuterait du code de la cible, cas que la leçon n'a pas mesurés.
Une troisième stratégie existe : compiler chaque plateforme sur un nœud natif (une machine arm64 pour l'arm64), puis assembler l'index avec docker buildx imagetools create. La leçon ne l'a pas mesurée, faute de machine arm64 : pour la mettre en œuvre, suivez la documentation Docker sur les builds multi-plateformes. La page Cache BuildKit en CI et promotion d'image entre registres emploie bien imagetools create, mais pour copier un index existant d'un registre à l'autre, pas pour assembler des builds natifs.
Pourquoi BUILDPLATFORM seul produit-il un binaire faux ?
Section intitulée « Pourquoi BUILDPLATFORM seul produit-il un binaire faux ? »Le piège naît d'un oubli : garder FROM --platform=$BUILDPLATFORM sans passer GOOS et GOARCH à go build. Go compile alors pour la machine qui construit, c'est-à-dire pour amd64, quelle que soit la plateforme visée.
FROM --platform=$BUILDPLATFORM golang:1.27-alpine@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3deb AS buildWORKDIR /srcCOPY go.mod main.go ./RUN CGO_ENABLED=0 go build -o /out/app .
FROM alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9aCOPY --from=build /out/app /appENTRYPOINT ["/app"]time docker buildx build --no-cache --platform linux/amd64,linux/arm64 -f Dockerfile.piege -t demo/plateformes:piege --load --progress=plain . 2>&1 | tail -25[...]#12 [linux/amd64 build 4/4] RUN CGO_ENABLED=0 go build -o /out/app .#12 DONE 4.1s[...]#15 DONE 0.7s
real 0m5.485sLa construction réussit, plus vite que les deux autres (une seule compilation, amd64, sert pour les deux plateformes : l'étape s'appelle [linux/amd64 build 4/4], sans flèche). L'index est correct : docker image inspect annonce bien linux/arm64 pour la seconde variante. C'est son contenu qui est faux.
docker image inspect --platform linux/arm64 --format '{{.Os}}/{{.Architecture}} id={{.Id}}' demo/plateformes:piegedocker run --rm --platform linux/arm64 demo/plateformes:piegemkdir -p out-piege-arm64 && docker create --platform linux/arm64 --name x-piege-arm64 demo/plateformes:piege >/dev/null && docker cp x-piege-arm64:/app out-piege-arm64/app && docker rm x-piege-arm64 >/dev/null && file out-piege-arm64/app && sha256sum out-piege-arm64/applinux/arm64 id=sha256:da09e9ebf5a8f6434dbd90e7691851e597358a682995e3139263d5a58d2c31c9GOOS=linux GOARCH=amd64 app=semrofetalpout-piege-arm64/app: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=8Hx6toIjGIVVZhE10x0l/yS1TKDtgP0hFNYOpW5iN/6pFQZ3Qn0lvYNzYf84bn/joVmo_R_xxFaEsEbCtzM, BuildID[sha1]=0eecf3c551407a2c4e8bd2386769f1de3d8aa2a5, with debug_info, not strippedf8e3629bcece470fd063e4fafe7ba50ed7de31b52babc68166781a7efd011c43 out-piege-arm64/appLisez les trois sorties ensemble. Le manifeste dit linux/arm64, mais le programme répond GOARCH=amd64, et file identifie un exécutable x86-64.
Sur cette VM amd64, la variante « arm64 » démarre sans erreur, parce que l'hôte exécute directement le binaire amd64 qu'elle contient. Sur un hôte arm64, le comportement n'a pas été mesuré. Seuls un lancement de chaque variante, ou un file sur chaque binaire, révèlent le défaut ; aucun message de construction n'en parle.
Que contient l'index publié dans un registre ?
Section intitulée « Que contient l'index publié dans un registre ? »Un index se publie avec --push, qui envoie le résultat à un registre au lieu de le charger en local. Pour l'essai, lancez un registre local qui n'écoute que sur la machine, sur le port 5055. L'image registry:3 est épinglée par digest.
docker run -d --name demo-registry -p 127.0.0.1:5055:5000 registry:3@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3 | cut -c1-12sleep 2curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5055/v2/edf8c2d24450200Au premier lancement, les lignes du tirage de l'image registry s'affichent avant l'identifiant. L'identifiant tronqué et le code 200 de l'adresse /v2/ montrent que le registre répond. Publiez ensuite l'index, avec le Dockerfile de compilation croisée (la progression de la construction n'est pas reproduite), puis lisez-le. Les digests de cette leçon seront différents chez vous : reprenez les vôtres, lus dans l'index.
docker buildx build --platform linux/amd64,linux/arm64 -f Dockerfile -t 127.0.0.1:5055/demo/plateformes:1.0 --push --progress=plain . 2>&1 | tail -12docker buildx imagetools inspect 127.0.0.1:5055/demo/plateformes:1.0Name: 127.0.0.1:5055/demo/plateformes:1.0MediaType: application/vnd.oci.image.index.v1+jsonDigest: sha256:c436d1a51d9234e3a0e81a8f9d7ad8aefd86ea3bf325a9ab6e5850addb409352
Manifests: Name: 127.0.0.1:5055/demo/plateformes:1.0@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202 MediaType: application/vnd.oci.image.manifest.v1+json Platform: linux/amd64
Name: 127.0.0.1:5055/demo/plateformes:1.0@sha256:c833aa8b36441c1ecdc60a928aef3f2520442958e7d5195d71e3c8b0459a5e5f MediaType: application/vnd.oci.image.manifest.v1+json Platform: linux/arm64
Name: 127.0.0.1:5055/demo/plateformes:1.0@sha256:4e7ae54f13010e50cc1e768c6f0b871808a1ed6a278db43fb72bcbd32b1a24b7 MediaType: application/vnd.oci.image.manifest.v1+json Platform: unknown/unknown Annotations: vnd.docker.reference.digest: sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202 vnd.docker.reference.type: attestation-manifest
Name: 127.0.0.1:5055/demo/plateformes:1.0@sha256:5b14946bee721bd75564a61ef3ae4fcc1ece8a7c565ff1cee1ccf93dc28d9367 MediaType: application/vnd.oci.image.manifest.v1+json Platform: unknown/unknown Annotations: vnd.docker.reference.type: attestation-manifest vnd.docker.reference.digest: sha256:c833aa8b36441c1ecdc60a928aef3f2520442958e7d5195d71e3c8b0459a5e5fDeux plateformes, quatre manifestes. Les deux premiers sont les variantes réelles. Les deux autres, de plateforme unknown/unknown, sont des attestations de provenance, des documents qui décrivent comment l'image a été construite. Buildx les demande par défaut et BuildKit les produit (l'option --provenance=false, vue plus bas, les supprime). L'annotation vnd.docker.reference.digest désigne le manifeste décrit, et vnd.docker.reference.type vaut attestation-manifest.
Le schéma montre cette structure. Suivez les traits pleins, qui mènent à ce qui s'exécute, puis les traits pointillés, qui relient chaque attestation au manifeste qu'elle décrit.
Un script de quelques lignes sépare les variantes des attestations. Il lit l'index brut et affiche, pour chaque entrée, son digest, sa plateforme et son type.
import json, sys# usage: digests.py [os/arch] (stdin = index brut) ; sans argument, liste toutidx = json.load(sys.stdin)want = sys.argv[1] if len(sys.argv) > 1 else Noneprint("entrees:", len(idx["manifests"]), file=sys.stderr)for m in idx["manifests"]: p = m.get("platform", {}) plat = p.get("os", "?") + "/" + p.get("architecture", "?") ann = m.get("annotations", {}) if want: if plat == want: print(m["digest"]) else: print(m["digest"], plat, ann.get("vnd.docker.reference.type", "-"), ann.get("vnd.docker.reference.digest", "-")[:19])docker buildx imagetools inspect --raw 127.0.0.1:5055/demo/plateformes:1.0 | python3 digests.pyentrees: 4sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202 linux/amd64 - -sha256:c833aa8b36441c1ecdc60a928aef3f2520442958e7d5195d71e3c8b0459a5e5f linux/arm64 - -sha256:4e7ae54f13010e50cc1e768c6f0b871808a1ed6a278db43fb72bcbd32b1a24b7 unknown/unknown attestation-manifest sha256:d75b98894278sha256:5b14946bee721bd75564a61ef3ae4fcc1ece8a7c565ff1cee1ccf93dc28d9367 unknown/unknown attestation-manifest sha256:c833aa8b3644Ne comptez jamais les entrées d'un index pour compter les plateformes : ici 4 entrées pour 2 plateformes. Lisez la plateforme de chaque entrée. Pour obtenir un index qui ne contient que les variantes, désactivez la provenance avec --provenance=false.
docker buildx build --platform linux/amd64,linux/arm64 -f Dockerfile -t 127.0.0.1:5055/demo/plateformes:1.0-nop --provenance=false --push --progress=plain . 2>&1 | tail -6docker buildx imagetools inspect --raw 127.0.0.1:5055/demo/plateformes:1.0-nop | python3 digests.pyentrees: 2sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202 linux/amd64 - -sha256:c833aa8b36441c1ecdc60a928aef3f2520442958e7d5195d71e3c8b0459a5e5f linux/arm64 - -Les deux variantes ont les mêmes digests que dans l'index précédent : seul l'index change. Le registre liste maintenant les deux tags.
curl -s http://127.0.0.1:5055/v2/demo/plateformes/tags/list{"name":"demo/plateformes","tags":["1.0","1.0-nop"]}Désactiver la provenance supprime une information utile à l'audit de la chaîne de construction. Faites-le quand un outil consommateur ne comprend pas les entrées unknown/unknown, pas par défaut.
Pourquoi un builder docker-container n'atteint-il pas 127.0.0.1 ?
Section intitulée « Pourquoi un builder docker-container n'atteint-il pas 127.0.0.1 ? »Le builder par défaut a publié sur 127.0.0.1:5055 sans difficulté. Un builder docker-container échoue sur la même adresse. Recréez le builder multi et tentez la même publication.
docker buildx create --name multi --driver docker-container \ --driver-opt image=moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c --usedocker buildx build --builder multi --platform linux/amd64,linux/arm64 -f Dockerfile -t 127.0.0.1:5055/demo/plateformes:cont --push . 2>&1 | tail -3multi > exporting to image:------ERROR: failed to build: failed to solve: failed to push 127.0.0.1:5055/demo/plateformes:cont: failed to do request: Head "http://127.0.0.1:5055/v2/demo/plateformes/blobs/sha256:6f4ccdb83c2f8f3bde1bc6328322cb52da372ec2260b9ee8a5295f42e2155bff": dial tcp 127.0.0.1:5055: connect: connection refusedLe message connection refused sur 127.0.0.1:5055 vient du fait que le builder tourne dans un conteneur, sur le réseau bridge de Docker. Pour lui, 127.0.0.1 est son propre conteneur, où personne n'écoute sur ce port, et non votre machine. Il faut recréer le builder en lui donnant le réseau de l'hôte avec --driver-opt network=host. L'option ne se change pas sur un builder existant.
docker buildx use defaultdocker buildx rm multidocker buildx create --name multi --driver docker-container \ --driver-opt network=host --driver-opt image=moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c --usedocker buildx build --builder multi --platform linux/amd64,linux/arm64 -f Dockerfile -t 127.0.0.1:5055/demo/plateformes:cont --push . 2>&1 | tail -3multi removedmulti#17 pushing manifest for 127.0.0.1:5055/demo/plateformes:cont@sha256:39ce05f2618949d78e22273cde8dd205bc40599a60070009c054fb3137c388c0#17 pushing manifest for 127.0.0.1:5055/demo/plateformes:cont@sha256:39ce05f2618949d78e22273cde8dd205bc40599a60070009c054fb3137c388c0 0.1s done#17 DONE 0.9sLa publication aboutit. En contrepartie, ce builder partage le réseau de la machine et peut joindre ses services locaux : gardez cette option pour les essais, et retirez le builder ensuite.
docker buildx use defaultdocker buildx rm multiLa commande rm répond multi removed.
Comment exécuter chaque variante de l'index ?
Section intitulée « Comment exécuter chaque variante de l'index ? »Un tag d'index n'est pas une preuve : il peut cacher une variante fausse. La preuve consiste à tirer chaque variante par le digest de son manifeste, lu dans l'index, puis à l'exécuter. Lisez d'abord les deux digests dans l'index publié, avec digests.py et son argument de plateforme, et rangez-les dans deux variables : aucun digest ne s'écrit à la main.
cd ~/multiarchAMD=$(docker buildx imagetools inspect --raw 127.0.0.1:5055/demo/plateformes:1.0 | python3 digests.py linux/amd64)ARM=$(docker buildx imagetools inspect --raw 127.0.0.1:5055/demo/plateformes:1.0 | python3 digests.py linux/arm64)echo "AMD=$AMD ARM=$ARM"entrees: 4entrees: 4AMD=sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202 ARM=sha256:c833aa8b36441c1ecdc60a928aef3f2520442958e7d5195d71e3c8b0459a5e5fLes digests des sorties qui suivent sont ceux de la mesure : les vôtres diffèrent, et les variables les portent à votre place. La ligne entrees: 4 part sur la sortie d'erreur, elle s'affiche mais n'entre pas dans la variable. Les variables n'existent que dans ce terminal : si vous le fermez, rejouez ce bloc tant que le registre tourne.
Supprimez ensuite les copies locales, pour que le test parte du registre.
docker image rm demo/plateformes:cross demo/plateformes:emul demo/plateformes:piege demo/plateformes:run-arm64 127.0.0.1:5055/demo/plateformes:1.0 127.0.0.1:5055/demo/plateformes:1.0-nopLa commande affiche une ligne Untagged: par image retirée. Tirez et exécutez ensuite la variante amd64, puis la variante arm64.
docker pull 127.0.0.1:5055/demo/plateformes@$AMD | tail -3docker run --rm --platform linux/amd64 127.0.0.1:5055/demo/plateformes@$AMDdocker image inspect --format '{{.Architecture}}' 127.0.0.1:5055/demo/plateformes@$AMDDigest: sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202Status: Downloaded newer image for 127.0.0.1:5055/demo/plateformes@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202127.0.0.1:5055/demo/plateformes@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202GOOS=linux GOARCH=amd64 app=semrofetalpamd64docker pull 127.0.0.1:5055/demo/plateformes@$ARM | tail -3docker run --rm --platform linux/arm64 127.0.0.1:5055/demo/plateformes@$ARMdocker image inspect --format '{{.Architecture}}' 127.0.0.1:5055/demo/plateformes@$ARMDigest: sha256:c833aa8b36441c1ecdc60a928aef3f2520442958e7d5195d71e3c8b0459a5e5fStatus: Downloaded newer image for 127.0.0.1:5055/demo/plateformes@sha256:c833aa8b36441c1ecdc60a928aef3f2520442958e7d5195d71e3c8b0459a5e5f127.0.0.1:5055/demo/plateformes@sha256:c833aa8b36441c1ecdc60a928aef3f2520442958e7d5195d71e3c8b0459a5e5fGOOS=linux GOARCH=arm64 app=semrofetalparm64Chaque exécution rend GOARCH égal à l'architecture annoncée par docker image inspect : le programme de la variante est celui de sa plateforme. Si l'une des deux lignes GOARCH avait été fausse, comme dans la variante du piège, vous l'auriez vu ici.
Que fait docker run --platform selon ce qui est déjà en local ?
Section intitulée « Que fait docker run --platform selon ce qui est déjà en local ? »La commande docker run --platform ne se comporte pas de la même façon selon ce que Docker a déjà en local pour cette image. Trois situations ont été mesurées, avec Docker 29.9.2 et le magasin containerd.
Dans la première, vous avez tiré un tag d'index avec une seule plateforme. Supprimez les copies par digest, puis tirez uniquement la variante amd64 de l'index sans attestations.
docker image rm 127.0.0.1:5055/demo/plateformes@$ARM 127.0.0.1:5055/demo/plateformes@$AMDdocker pull --platform linux/amd64 127.0.0.1:5055/demo/plateformes:1.0-nopdocker image ls --tree | sed -n '1p;/plateformes/,+3p'L'arborescence se lit avec docker image ls --tree : la variante arm64 y figure avec 0B, c'est-à-dire déclarée par l'index mais absente du disque.
IMAGE ID DISK USAGE CONTENT SIZE EXTRA127.0.0.1:5055/demo/plateformes:1.0-nop e804613147cb 16.8MB 5.3MB├─ linux/amd64 d75b98894278 16.8MB 5.3MB└─ linux/arm64 c833aa8b3644 0B 0BLancez maintenant la variante arm64, qui n'est pas en local.
docker run --rm --platform linux/arm64 127.0.0.1:5055/demo/plateformes:1.0-nopUnable to find image '127.0.0.1:5055/demo/plateformes:1.0-nop' locally1.0-nop: Pulling from demo/plateformese5c2b96a0c80: Pulling fs layerd3531b7a4f21: Pulling fs layere5c2b96a0c80: Already existsd3531b7a4f21: Already existse5c2b96a0c80: Pull completeDigest: sha256:e804613147cb4fb45c7ef6f9619df1abd15a2b184e923469f2dc5569e4722b61Status: Image is up to date for 127.0.0.1:5055/demo/plateformes:1.0-nopGOOS=linux GOARCH=arm64 app=semrofetalpDocker a tiré la couche manquante de la variante arm64 et l'a lancée. Le message est trompeur : Status: Image is up to date désigne l'index, déjà présent, et non la variante, qui vient d'être téléchargée (la ligne e5c2b96a0c80: Pull complete en témoigne). Le lancement sans --platform reste sur la plateforme de la machine.
docker run --rm 127.0.0.1:5055/demo/plateformes:1.0-nopGOOS=linux GOARCH=amd64 app=semrofetalpDans la deuxième situation, l'image locale est un manifeste unique, tiré par son digest. Aucun index ne permet de trouver une autre variante. Supprimez le tag d'index, tirez la variante amd64 par digest et demandez arm64.
docker image rm 127.0.0.1:5055/demo/plateformes:1.0-nopdocker pull 127.0.0.1:5055/demo/plateformes@$AMD | tail -2docker run --rm --platform linux/arm64 127.0.0.1:5055/demo/plateformes@$AMDecho "code=$?"Status: Downloaded newer image for 127.0.0.1:5055/demo/plateformes@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202127.0.0.1:5055/demo/plateformes@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202Unable to find image '127.0.0.1:5055/demo/plateformes@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202' locally127.0.0.1:5055/demo/plateformes@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202: Pulling from demo/plateformesDigest: sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202Status: Image is up to date for 127.0.0.1:5055/demo/plateformes@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202docker: Error response from daemon: image with reference 127.0.0.1:5055/demo/plateformes@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202 was found but does not provide the specified platform (linux/arm64)
Run 'docker run --help' for more informationcode=125Cette fois Docker échoue avec le code 125 et le message was found but does not provide the specified platform (linux/arm64). Un digest de manifeste désigne une seule plateforme : il n'y a pas de variante à aller chercher. Le même message s'observe, avec --pull=never, sur une image locale à manifeste unique : mesuré sur l'image Alpine tirée en amd64 seule.
Dans la troisième situation, l'image vient d'être construite en local pour une seule plateforme, sous un tag qui ne correspond à aucun registre.
docker buildx build --platform linux/amd64 -f Dockerfile -t demo/plateformes:amd64only --load . 2>&1 | tail -3docker run --rm --platform linux/arm64 demo/plateformes:amd64onlyecho "code=$?"#12 naming to docker.io/demo/plateformes:amd64only done#12 unpacking to docker.io/demo/plateformes:amd64only 0.0s done#12 DONE 0.1sUnable to find image 'demo/plateformes:amd64only' locallydocker: Error response from daemon: pull access denied for demo/plateformes, repository does not exist or may require 'docker login'
Run 'docker run --help' for more informationcode=125Docker ne trouve pas la plateforme demandée en local, tente de la tirer depuis le registre que le nom désigne (Docker Hub, pour demo/plateformes) et reçoit pull access denied. Le code 125 est encore celui de Docker lui-même, pas du programme. Le tableau résume les trois cas.
| Ce qui est en local | Ce que fait docker run --platform linux/arm64 | Ce que vous lisez |
|---|---|---|
Tag d'index tiré en amd64 seul | Tire la variante arm64 et la lance | Image is up to date, puis GOARCH=arm64 |
Manifeste unique amd64, référence par digest | Échoue : l'image ne fournit pas cette plateforme | Code 125, was found but does not provide the specified platform |
Image construite en local pour amd64 seul | Cherche la plateforme dans un registre qui ne l'a pas | Code 125, pull access denied |
Pour les deux premiers cas, la parade tient en deux gestes : tirer la plateforme voulue avec docker pull --platform, puis vérifier l'architecture avec docker image inspect --platform et le lancement lui-même, plutôt que de se fier au message Image is up to date. Pour le troisième, construisez la plateforme voulue, ou publiez l'index et tirez-le.
Comment déclarer les plateformes que vous n'avez pas rejouées ?
Section intitulée « Comment déclarer les plateformes que vous n'avez pas rejouées ? »Tout ce que cette leçon a exécuté l'a été sur une seule machine amd64, avec l'arm64 émulé par QEMU. Exécuter l'image sous émulation prouve que le binaire arm64 démarre et répond ; cela ne prouve pas son comportement sur un processeur arm64 réel, que la leçon n'a pas mesuré. Une publication honnête dit, plateforme par plateforme, ce qui a été construit, ce qui a été exécuté et où.
| Plateforme | Construite | Exécutée | Où | Ce que cela prouve |
|---|---|---|---|---|
linux/amd64 | oui | oui | en natif, sur la VM de mesure | le binaire x86-64 répond GOARCH=amd64 |
linux/arm64 | oui | oui | sous QEMU, sur la même VM | le binaire ARM aarch64 démarre et répond GOARCH=arm64 ; rien sur un hôte arm64 |
autres (386, s390x) | non | non | rien |
Une plateforme que vous annoncez dans l'index sans l'avoir exécutée doit figurer comme telle dans ce tableau, ou ne pas être publiée. La construction de linux/s390x avec Dockerfile.run ou Dockerfile.emul, qui exécutent un RUN sous la plateforme cible, échoue d'ailleurs sur cette machine, parce que seul l'émulateur arm64 y est enregistré (voir le tableau de dépannage). Pour qualifier une plateforme, exécutez-la sur du matériel de cette architecture, par exemple depuis une CI qui dispose de machines arm64. Cette leçon n'a pas rejoué ce cas.
Deux guides prolongent cette leçon. Pour un cluster qui mélange les deux architectures, voir Mixer des nœuds ARM et AMD64 dans un cluster k3s ; pour orchestrer plusieurs constructions depuis un fichier, voir Docker Bake.
Que faire quand une construction ou un lancement multi-plateforme échoue ?
Section intitulée « Que faire quand une construction ou un lancement multi-plateforme échoue ? »Chaque erreur du tableau a été provoquée lors des mesures, avec Docker 29.9.2 et Buildx 0.38.1. Lisez la colonne de gauche comme le symptôme que vous voyez, et la dernière comme le geste qui le corrige.
| Symptôme | Cause | Solution |
|---|---|---|
exec /bin/uname: exec format error, code 255, au docker run --platform linux/arm64 | Aucun émulateur arm64 enregistré dans binfmt_misc | Enregistrer l'émulation avec tonistiigi/binfmt et --install arm64 |
exec /bin/sh: exec format error et exit code: 255 à une étape RUN d'une construction | Le RUN exécute un programme de la plateforme cible, sans émulateur | Enregistrer l'émulation, ou compiler en croisé et ne garder que des COPY dans l'étape finale |
La construction --platform linux/s390x d'un Dockerfile qui exécute un RUN sous la plateforme cible échoue en 255 alors que l'arm64 fonctionne | Seul qemu-aarch64 est enregistré | Enregistrer l'émulateur de cette architecture, ou ne pas la viser |
failed to push 127.0.0.1:5055/…: connection refused avec un builder docker-container | Le builder s'exécute sur le réseau bridge : 127.0.0.1 est son propre conteneur | Recréer le builder avec --driver-opt network=host |
Status: Image is up to date alors que la variante arm64 vient d'être téléchargée | Le message concerne l'index, déjà local, pas la variante | Vérifier avec docker image ls --tree et docker image inspect --platform |
was found but does not provide the specified platform (linux/arm64), code 125 | L'image locale est un manifeste unique d'une autre plateforme | Tirer l'index avec docker pull --platform linux/arm64, ou le digest de la bonne variante |
pull access denied for demo/plateformes, code 125, à un docker run --platform | L'image locale n'a pas la plateforme et Docker cherche un registre qui ne la contient pas | Construire la plateforme voulue, ou publier l'index et le tirer |
Les deux variantes répondent GOARCH=amd64, file annonce x86-64 partout | FROM --platform=$BUILDPLATFORM sans GOOS=$TARGETOS GOARCH=$TARGETARCH dans go build | Passer TARGETOS et TARGETARCH à la compilation, puis lancer chaque variante |
| 4 entrées dans l'index pour 2 plateformes | Deux attestations de provenance, de plateforme unknown/unknown | Lire la plateforme de chaque entrée ; --provenance=false si un outil les refuse |
Comment tout nettoyer ?
Section intitulée « Comment tout nettoyer ? »La démonstration laisse un registre, des images, une émulation, un builder éventuel, des fichiers et du cache. Retirez-les dans cet ordre, sans rm -r : chaque suppression nomme ce qu'elle retire. La variable AMD sert à supprimer une image plus bas : si vous avez changé de terminal, recalculez-la avant d'arrêter le registre. Commencez par le registre et le builder.
docker buildx lsdocker volume lsdocker rm -f -v demo-registrydocker image rm registry@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3docker volume lsNAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMSdefault* docker \_ default \_ default running v0.34.0 linux/amd64 (+3), linux/arm64DRIVER VOLUME NAMElocal f7ca5e2a38bef88fa3326fea36e7e4e3c573b507460c1127ecae2d5489df1916demo-registryUntagged: registry@sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3Deleted: sha256:d4032d4cc323c1731c707931ecada07a53076eac38e3b61ae57533f170f2c6c3DRIVER VOLUME NAMEL'image registry:3 déclare un volume anonyme, que docker rm -f seul laisse en place. L'option -v le retire avec le conteneur : comparez les deux listes de docker volume ls, le volume du registre disparaît de la seconde et vos autres volumes y restent. Si docker buildx ls montre encore le builder multi, retirez-le ; s'il n'existe plus, docker buildx rm répond no builder "multi" found, sans conséquence.
docker buildx use defaultdocker buildx rm multiRetirez ensuite l'émulation, puis les images de la leçon. Ne lancez la première commande que si la liste relevée au début (le ls de /proc/sys/fs/binfmt_misc) ne contenait pas déjà qemu-aarch64 : sinon, vous retireriez une émulation qui existait avant la leçon.
docker run --privileged --rm tonistiigi/binfmt:qemu-v10.2.3-68@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 --uninstall qemu-aarch64ls /proc/sys/fs/binfmt_miscdocker image rm demo/plateformes:amd64only 127.0.0.1:5055/demo/plateformes@$AMD tonistiigi/binfmt:qemu-v10.2.3-68@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c golang@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3debuninstalling: qemu-aarch64 OK{ "supported": [ "linux/amd64", "linux/amd64/v2", "linux/amd64/v3", "linux/386" ], "emulators": [ "python3.13" ]}python3.13registerstatusUntagged: demo/plateformes:amd64onlyDeleted: sha256:f8dd24ec566157b5199cf2be50e75e782e8f4f58342249ba4684ac552dde2e99Untagged: 127.0.0.1:5055/demo/plateformes@sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202Deleted: sha256:d75b98894278e727eb1752efc3677cb3f242c13191d427acb33323b4493c4202Untagged: tonistiigi/binfmt@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0Deleted: sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0Untagged: moby/buildkit@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87cDeleted: sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87cUntagged: golang@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3debDeleted: sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3debContrôlez l'état du noyau avec ls /proc/sys/fs/binfmt_misc, qui ne doit plus lister qemu-aarch64. Ne vous fiez pas à docker buildx ls pour cela : après le retrait, le builder par défaut y affichait encore linux/arm64 dans les mesures. Cette liste est restée en cache jusqu'au redémarrage du moteur ; après sudo systemctl restart docker, elle est revenue à linux/amd64, linux/amd64/v2 et linux/amd64/v3. Le vrai contrôle reste /proc/sys/fs/binfmt_misc.
Attention : docker builder prune -af vide tout le cache de construction du builder, pas seulement celui de cette leçon. Sautez cette commande si d'autres projets en dépendent.
docker builder prune -afdocker image lsID RECLAIMABLE SIZE LAST ACCESSEDqqfegccgz1itogk5io3szu2lf* true 36.43MB About a minute ago2e807td3hxxvpgioms1xqm33l* true 36.4MB About a minute ago[...]Total: 959.4MBIMAGE ID DISK USAGE CONTENT SIZE EXTRAalpine:latest be2656061b53 26.6MB 8.22MBalpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a be2656061b53 26.6MB 8.22MBLa ligne alpine:latest appartient à l'état de départ de la machine de mesure : elle n'a pas été créée par la leçon, et la vôtre peut différer.
Supprimez enfin les fichiers de la leçon, un par un, puis le dossier vide.
cd ~/multiarch && for d in out-*; do rm $d/app; rmdir $d; done; rm Dockerfile Dockerfile.emul Dockerfile.piege Dockerfile.run go.mod main.go digests.py; cd ~; rmdir multiarchL'image Alpine peut rester : elle sert à d'autres leçons, et elle garde la variante arm64 tirée au début de celle-ci. Si vous voulez aussi la retirer, docker image rm suivi de sa référence suffit.
Mettre en pratique
Section intitulée « Mettre en pratique »Le lab part d'un petit programme à livrer pour deux architectures. Vous publiez dans un registre local un index qui référence deux variantes réelles, puis vous exécutez chacune des deux. Seul l'état du système est contrôlé, avec dsoxlab check, jamais les commandes tapées : la validation lit l'index publié, vérifie qu'il contient bien les deux plateformes demandées, et contrôle à l'exécution que le binaire de chaque variante est celui de son architecture.
À retenir
Section intitulée « À retenir »-
Dans un Dockerfile, seules les instructions
RUNlancent un programme de la plateforme cible : ce sont elles qui exigent l'émulation.FROMetCOPYne lancent aucun programme. Sans QEMU enregistré, unRUNd'une autre architecture échoue avecexec format erroret le code 255. -
L'émulation s'enregistre et se retire avec
docker run --privileged --rm tonistiigi/binfmt:qemu-v10.2.3-68@sha256:… --install arm64puis--uninstall qemu-aarch64, et se vérifie dans/proc/sys/fs/binfmt_misc. -
Un builder
docker-containers'ajoute et se retire (docker buildx create,docker buildx rm, puisdocker image rmde l'image BuildKit épinglée). Sur le réseaubridge, il ne joint pas127.0.0.1de l'hôte : ajoutez--driver-opt network=host. -
Sur le magasin containerd, le builder par défaut construit deux plateformes avec
--platform linux/amd64,linux/arm64 --load;docker image ls --treeetdocker image inspect --platformlisent chaque variante. -
La compilation croisée est plus rapide que l'émulation : 8,9 s contre 56,8 s sur la VM de mesure, avec
FROM --platform=$BUILDPLATFORM,TARGETOSetTARGETARCH.BUILDPLATFORMsansGOARCHdonne un index juste et un binaire faux. -
Ne comptez jamais les entrées d'un index : 4 entrées pour 2 plateformes, dont 2 attestations
unknown/unknown. Tirez chaque variante par le digest de son manifeste et exécutez-la. -
docker run --platformdépend de ce qui est local : un index partiel tire la variante manquante sous un messageImage is up to datetrompeur, un manifeste unique donne le code 125. Tirez avecdocker pull --platform, puis vérifiez à l'exécution. -
Un test sous émulation n'est pas une qualification sur du matériel natif : déclarez, plateforme par plateforme, ce qui a été construit, exécuté, et où.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Épreuve : reconstruire une image pour deux architectures
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications