docker build && docker push depuis votre poste fonctionne tant que personne d’autre n’a besoin de publier la même image, ou tant que cela n’a pas à se produire à chaque merge sans que vous soyez devant le clavier. GitHub Actions exécute cette build sur les runners de GitHub, tague l’image selon ce qui a déclenché le workflow, et la publie sur un registry depuis lequel votre étape de déploiement peut faire un pull. Rien ne tourne en local.
Ce qu’il faut pour construire et publier une image Docker depuis la CI
Cinq actions doivent être en place avant qu’un runner puisse publier une image :
actions/checkoutpour le contexte de build : votre Dockerfile et votre code sourcedocker/setup-buildx-actionpour un builder BuildKit, car le Docker Engine de base du runner ne peut ni exporter le cache des layers ni construire pour d’autres plateformesdocker/login-actionpour les identifiants du registrydocker/metadata-actionpour transformer le ref Git en liste de tags et en labels OCIdocker/build-push-actionpour exécuter la build et le push en un seul appel
Docker maintient les cinq, et elles sont conçues pour s’enchaîner : les sorties de l’étape metadata alimentent directement les entrées de l’étape build. En dessous, c’est le même docker build et docker push que vous lancez à la main, déplacé sur un runner et branché sur les événements Git.
Pourquoi le runner a besoin de Buildx avant de pouvoir construire quoi que ce soit
ubuntu-latest inclut Docker Engine, et Docker Engine construit des images sans problème avec docker build. Ce qu’il ne peut pas faire seul, c’est exporter un cache de build ailleurs que sur le disque local, ou construire pour une plateforme différente de celle du runner. docker/setup-buildx-action crée un builder Buildx adossé à BuildKit et le définit par défaut, ce qui rend possibles cache-to/cache-from et les builds multi-plateformes plus loin dans ce guide.
| |
Une seule étape, sans entrée nécessaire dans le cas courant. Si vous la sautez, build-push-action tourne quand même, avec le builder déjà présent sur le runner — la build réussit, mais l’export de cache et le multi-arch ne sont plus disponibles.
Comment s’authentifier sur GHCR avec le token intégré
Chaque exécution du workflow reçoit un GITHUB_TOKEN de courte durée, limité au dépôt dans lequel il tourne. Pour publier sur la GitHub Container Registry (ghcr.io), ce token suffit : pas de personal access token, pas de secret à créer ni à faire tourner.
| |
Le bloc permissions compte autant que l’étape de login. Sans packages: write, le token que GitHub remet au workflow peut lire mais pas publier, donc le login réussit et le push échoue avec un 403. Sur un dépôt d’organisation, il y a un second endroit à vérifier : un paramètre de permissions des workflows sous Settings → Actions → General peut limiter les tokens à la lecture seule, quoi que demande le fichier du workflow. Si le push échoue encore avec le bloc ci-dessus déjà en place, vérifiez ce paramètre.
Comment taguer les images automatiquement depuis le ref Git
Fixer un tag en dur dans le fichier du workflow oblige à modifier le YAML à chaque release. docker/metadata-action lit l’événement qui a déclenché l’exécution (quelle branche, quel tag, quelle PR) et produit la liste des tags et les labels OCI à votre place.
| |
Vous publiez un tag v1.4.0 et la règle semver produit 1.4.0. Vous poussez sur main et la règle raw ajoute latest. Chaque build reçoit un tag basé sur le short-SHA quel que soit le déclencheur, si bien qu’un commit précis reste récupérable même après que latest a avancé au-delà. steps.meta.outputs.tags est une liste de tags séparés par des sauts de ligne produite par ces trois règles pour cet événement, pas l’union des trois sur l’ensemble des exécutions.
À quoi ressemble le workflow complet
Assemblez les pièces et ajoutez docker/build-push-action, qui exécute docker build et docker push en un seul appel BuildKit :
| |
context: . construit depuis le dépôt tout juste récupéré en utilisant le Dockerfile qui s’y trouve ; pointez file: vers un autre chemin si le vôtre est ailleurs. Ce qui finit dans l’image reste l’affaire du Dockerfile, de l’ordre des layers à un USER non root. Ce workflow décide seulement quand la build tourne et où va le résultat.
Une chose à régler tout de suite : si la build elle-même a besoin d’un identifiant, par exemple un token pour un registry npm privé, passez-le par l’entrée secrets: de l’action plutôt que par un build-arg, qui reste visible dans l’historique de l’image. Variables d’Environnement et Secrets dans Docker détaille cette différence.
Comment mettre les layers en cache entre les exécutions du workflow
Un runner hébergé par GitHub démarre propre à chaque fois. Sans cache externe, chaque layer se reconstruit à chaque exécution, RUN npm ci compris. cache-from: type=gha et cache-to: type=gha,mode=max dans l’étape ci-dessus indiquent à BuildKit de lire et d’écrire son cache via le service de cache de GitHub Actions plutôt que sur le disque local, si bien que le cache survit au runner.
mode=max met en cache chaque layer intermédiaire, y compris ceux des étapes précédentes d’un Dockerfile multi-stage. Le défaut, mode=min, ne garde que les layers qui finissent dans l’image finale, ce qui veut dire que l’étape de build d’un Dockerfile multi-stage recompile de zéro à chaque exécution.
Le cache Actions a un plafond de taille par dépôt et évince les entrées les plus anciennes selon une logique LRU. Pour la plupart des images applicatives, cela ne pose jamais problème. Un dépôt qui construit plusieurs grandes images multi-arch peut l’atteindre, et un cache adossé à un registry (cache-to: type=registry,ref=ghcr.io/org/app:buildcache) est la solution, car il n’est pas plafonné par le quota du cache Actions.
Comment construire à chaque push mais ne publier qu’à la release
Le workflow ci-dessus publie à chaque push sur main et à chaque tag de version, ce qui est le bon défaut pour un projet qui déploie depuis main. Une configuration plus prudente construit et teste chaque pull request sans rien publier, et ne publie qu’après un merge :
| |
La build complète tourne quand même sur la PR, donc un Dockerfile cassé échoue avant le merge, tandis que push: false saute l’écriture sur le registry. Avec cache-to: type=gha déjà en place, cette build de PR prépare aussi le cache que le push post-merge réutilisera.
Ce qui change si vous publiez sur Docker Hub plutôt que GHCR
Docker Hub demande un argument de registry différent et des identifiants différents : un access token depuis Account Settings → Security, pas le mot de passe de votre compte, que Docker Hub n’accepte plus pour les connexions via API.
| |
La configuration de Buildx, l’extraction des metadata et l’étape build-push ne changent pas. Le registry est un paramètre, pas un pipeline différent.
Ce que ce workflow ne couvre pas
Les images multi-plateformes (linux/amd64 plus linux/arm64 dans un seul manifest) nécessitent docker/setup-qemu-action avant Buildx et une entrée platforms: dans l’étape de build. L’émulation QEMU rend une build arm64 sur un runner amd64 lente, souvent plusieurs fois plus lente que du natif. Si vous construisez régulièrement en arm64, les runners arm64 hébergés par GitHub suppriment complètement l’émulation.
Les runners self-hosted qui ont déjà Buildx configuré n’ont pas besoin de setup-buildx-action, et un builder persistant là-bas peut battre le backend de cache Actions en gardant les layers sur disque local entre les exécutions. Un monorepo qui publie trois images de service passe généralement à une matrix de build plutôt qu’à trois copies de ce job, ce qui change la façon dont context et images sont paramétrés mais pas les étapes elles-mêmes.
Comment tester le workflow avant de taguer une release
Poussez d’abord sur une branche, pas sur un tag de version. Regardez l’exécution dans l’onglet Actions, puis vérifiez l’image sur la page Packages de votre dépôt, ou faites le pull directement avec docker pull ghcr.io/<owner>/<repo>:<short-sha> en utilisant le tag du log de l’exécution. Une fois ce pull réussi, une release est le même git tag v1.0.0 && git push --tags que vous utilisez déjà, et le workflow prend le relais à partir de là.