Skip to content

Publier automatiquement des images multi-arch sur le DockerHub

Pour l’un de mes side projects (une petite webapp), je suis arrivé à l’étape où je devais construire mon image Docker pour embarquer l’application. L’objectif était de la rendre publique pour que n’importe qui puisse l’utiliser facilement.

Plutôt que de la builder uniquement pour l’architecture AMD, je me suis dit : « Pourquoi ne pas la rendre aussi compatible ARM ?”, comme ça pas de jaloux, tout le monde pourra l’utiliser, même ceux sous Mac M1/M2… 😛

Je me suis donc penché sur la question du build multi-platform.

Comprendre le build multi-platform

Avant d’aller plus loin, revenons plus en détail sur le fonctionnement d’un build multi-platform. 

Depuis quelques années maintenant, on voit de plus en plus de machines tourner sous ARM que ce soit des Raspberry, des Macs M1/M2/M3/M4 ou des instances AWS Graviton. Mais en parallèle, la majorité des PC et serveurs tournent encore sur de l’AMD64.

Ce sont deux architectures CPU différentes. Je ne vais pas vous faire un cours d’architecture processeur, je suis incapable de vous l’expliquer correctement mdr. Mais retenez juste que ce n’est pas du tout la même chose 🤷‍♂️.

Sinon, voici la réponse de ChatGPT

ARM : utilise une architecture appelée RISC (Reduced Instruction Set Computing).
→ C’est une architecture plus simple, optimisée pour consommer peu d’énergie.
→ Très utilisée dans les smartphones, tablettes, objets connectés, et maintenant certains PC (comme les Mac M1/M2/M3).

AMD : fabrique des processeurs avec l’architecture x86 (ou x86-64, plus précisément).
→ Plus complexe, mais puissante, adaptée aux PC, consoles de jeux, et serveurs.

Et plus les plus curieux je vous laisserai creuser le sujet 😂

Cela étant dit, si vous avez déjà un peu joué avec Docker, vous voyez peut-être venir le problème. 😏

Docker partage le kernel de l’hôte. Du coup, si vous construisez une image pour AMD64, vous ne pouvez pas la faire tourner directement sur une machine ARM. Sauf passer par de l’émulation mais c’est pas ouf.

C’est là que le build multi-platform entre en scène. 💃

L’idée c’est de packager plusieurs versions de votre application dans une seule image Docker. En gros, 1 version = 1 arch CPU.

Doooooonc, quand un utilisateur exécute docker pull depuis sa machine, le client Docker choisit automatiquement la bonne version de l’image en fonction de son architecture (ARM ou AMD). Et ça, c’est franchement top parce-que l’on a plus besoin de maintenir deux images (ou plus), ni de forcer les gens à savoir laquelle télécharger.

Ok, donc on peut avoir plusieurs images dans une seule image ? Comment c’est possible ton truc ? 🤔

Une image Docker « classique » contient un seul manifest qui lui même contient son fichier de config + son jeu de layers (les instructions du Dockerfile).

Dans le cas d’une image multi-platform, elle contient un index, qui pointe vers plusieurs manifests. Chaque manifest est spécifique à une architecture (ARM64, AMD64, etc.).

C’est cette structure qui permet à Docker de faire le tri automatiquement et de tirer la bonne version selon la machine de l’utilisateur.

Si on schématise, ça donne ça :

Fonctionnement des index

Si vous voulez comprendre un peu plus le fonctionnement des index je vous laisse avec la doc de docker qui est tipetop.

Et si on automatisait tout ça ?

Je n’aime pas trop trop me répéter alors j’ai poussé le truc un peu plus loin. Je me suis demandé si je ne pouvais pas automatiser le build multi platform de ma petite appli avec GitLab CI (vu que j’héberge déjà le code sur cette plateforme). En fouillant un peu, je suis tombé sur cet article .

Franchement, niquel, ça montre qu’il est possible de faire des build multi-plateform avec GitLab CI et l’exemple de l’article fonctionne très bien ! Mais comme souvent avec les exemples “de base” tout est codé en dur (les plateformes sur lesquelles on veut build, la registry dans laquelle on veut push…). Du coup, j’ai décidé de prendre cet exemple comme point de départ et de le rendre plus souple et plus réutilisable. Pour ça, j’ai variabilisé au max (plateformes, tag, registry, creds) pour pouvoir intégrer ce job dans n’importe quel projet. Je ne veux pas avoir à toucher au script à chaque build ou du moins le moins possible !

Eeeeeeeet, roulement de tambours 🥁
ça donne ça :

.build-multi-arch:
  image: docker:27.4.1
  services:
    - docker:dind
  before_script:
    - test -z $DOCKER_HUB_PAT && echo -e "\e[31m DOCKER_HUB_PAT variable is required to login to DockerHub" && exit 1
    - test -z $DOCKER_HUB_USER && echo -e "\e[31m DOCKER_HUB_USER variable is required to login to DockerHub" && exit 1
    - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin
  script:
    - docker buildx create --use
    - docker buildx build --push --platform $IMAGE_PLATFORMS --tag $PUSH_TO_REPO:$IMAGE_TAG .

Il n’y a rien de foufou au final. On fait du Docker in Docker pour avoir accès aux commandes docker. Ensuite, j’ai ajouté un before_script pour m’assurer que les variables sont définies pour ce connecter sur le DockerHub. Enfin, on build et on push en fonction des plateformes spécifiées et de la registry définies via les variables.

J’ai tout documenté ici. Pour que ce soit encore plus simple à utiliser, j’ai créé un template GitLab CI. Le job peut être intégré dans vos pipelines si vous souhaitez build et publier vos images sur le DockerHub avec plusieurs plateformes. 😎

Pour vous la faire giga courte, il suffit d’ajouter ce bloc dans vos pipelines :

include:
  - https://gitlab.com/Gribhb/am-ci-templates/-/raw/main/jobs/docker/build-multi-arch.yml

build-and-push:
  stage: build
  extends: .build-multi-arch
  variables:
    PUSH_TO_REPO: ""
    IMAGE_TAG: ""
    IMAGE_PLATFORMS: ""

Conclusion

Voilà, c’était un petit article sans prétention pour vous partager mon exploration autour des builds multi-plateform avec Docker et GitLab CI, suite à un besoin perso.

Ça m’a permis de me challenger un peu pour arriver à builder puis publier des images compatibles avec plusieurs plateformes en seulement quelques lignes dans le fichier gitlab-ci. Mission accomplie ! 🎯 (je trouve mdr)

Bon, c’est sûrement perfectible, mais le résultat est là. On peut avoir des déploiements d’images homogènes prêts à être utilisées aussi bien sur Raspberry Pi que sur Mac M1/M2 ou un serveur x86.

Et oui, le plus important ! N’hésitez pas à le forker, à l’adapter à vos besoins et à faire des PR. Je suis preneur de toutes évolutions 🙏

👉 Le code source du template est disponible ici

Published inNon classé