Un pipeline CI qui reconstruit l’image Docker de base à chaque exécution, même quand le Dockerfile n’a pas bougé depuis des semaines, gaspille un temps précieux : sur un projet de l’agence, ce rebuild systématique ajoutait environ trois minutes quarante à chaque exécution du pipeline de déploiement, pour un résultat strictement identique à l’image déjà poussée au registre la veille.
Ce texte ne traite pas du registre de conteneurs privé lui-même, déjà couvert par ailleurs. Il s’agit ici d’une condition ajoutée en amont dans le pipeline : détecter que le Dockerfile n’a pas changé, et dans ce cas, sauter purement et simplement l’étape de build pour réutiliser l’image déjà existante au registre.
Le principe : un hash comme empreinte de décision
L’idée consiste à calculer une empreinte (un hash SHA-256) du contenu du Dockerfile, et à comparer cette empreinte à celle utilisée lors du dernier build réussi, stockée quelque part de façon persistante entre deux exécutions du pipeline. Si l’empreinte est identique, le Dockerfile n’a pas changé et l’image existante convient encore. Si elle diffère, un rebuild s’impose.
Stocker l’empreinte du dernier build
GitHub Actions propose un mécanisme de cache natif, actions/cache, qui convient parfaitement pour persister un simple fichier texte contenant le dernier hash connu entre deux exécutions :

jobs:
verifier-et-construire:
runs-on: ubuntu-latest
outputs:
rebuild_necessaire: ${{ steps.comparaison.outputs.rebuild }}
steps:
- uses: actions/checkout@v4
- name: Calculer le hash actuel du Dockerfile
id: hash
run: echo "actuel=$(sha256sum Dockerfile | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
- name: Récupérer le dernier hash connu
uses: actions/cache@v4
with:
path: .dernier-hash-dockerfile
key: hash-dockerfile-${{ steps.hash.outputs.actuel }}
restore-keys: hash-dockerfile-
- name: Comparer les deux hashs
id: comparaison
run: |
if [ -f .dernier-hash-dockerfile ] && \
[ "$(cat .dernier-hash-dockerfile)" = "${{ steps.hash.outputs.actuel }}" ]; then
echo "rebuild=non" >> "$GITHUB_OUTPUT"
echo "Dockerfile inchangé, rebuild inutile."
else
echo "rebuild=oui" >> "$GITHUB_OUTPUT"
echo "${{ steps.hash.outputs.actuel }}" > .dernier-hash-dockerfile
echo "Dockerfile modifié ou premier build, rebuild nécessaire."
fi
Pourquoi la clé de cache inclut déjà le hash
La clé de cache hash-dockerfile-${{ steps.hash.outputs.actuel }} intègre volontairement le hash courant. Si ce hash a déjà servi de clé lors d’un run précédent, actions/cache restaure directement le fichier correspondant sans même nécessiter la comparaison manuelle qui suit, ce qui rend la vérification redondante mais robuste : même en cas d’incohérence rare entre le cache et le fichier restauré, la comparaison explicite tranche en dernier recours.
Conditionner le job de build sur ce résultat
Le job de construction proprement dit ne se déclenche que si la sortie rebuild_necessaire vaut oui :
construire-image:
needs: verifier-et-construire
if: needs.verifier-et-construire.outputs.rebuild_necessaire == 'oui'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Connexion au registre
run: echo "${{ secrets.REGISTRE_TOKEN }}" | docker login registre.agence.example -u agence --password-stdin
- name: Construire et pousser
run: |
docker build -t registre.agence.example/base/php-fpm-wordpress:latest .
docker push registre.agence.example/base/php-fpm-wordpress:latest
Le job suivant, celui qui déploie effectivement le site, ne dépend jamais directement de ce job de build : il pointe simplement vers le tag latest de l’image au registre, qu’elle vienne d’être reconstruite à l’instant ou qu’elle date de plusieurs semaines, ce qui rend le mécanisme totalement transparent pour la suite du pipeline.
Le piège à éviter : ignorer les dépendances indirectes
Un Dockerfile qui référence un fichier externe, par exemple une liste d’extensions PHP dans un fichier php-extensions.txt copié dans l’image, peut rester inchangé lui-même alors que ce fichier annexe a évolué. Le hash calculé uniquement sur le Dockerfile manquerait alors ce changement. La correction consiste à inclure dans le calcul de hash tous les fichiers réellement pertinents pour le build :
echo "actuel=$(cat Dockerfile php-extensions.txt docker/*.conf | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
Sur un projet de l’agence, cet oubli initial a laissé passer une modification de la configuration PHP-FPM pendant plusieurs jours avant d’être repérée, le pipeline continuant benoîtement à réutiliser une image obsolète parce que seul le Dockerfile faisait partie du calcul de hash.
Mesurer le gain réel
| Scénario | Durée du job |
|---|---|
| Rebuild systématique (avant) | 4 min 05 |
| Rebuild sauté grâce au cache (après, cas fréquent) | 0 min 25 |
| Rebuild réel nécessaire (Dockerfile modifié) | 4 min 10 |
Reconstruire une image identique à celle déjà poussée la veille ne teste rien de plus, ça consomme juste du temps de CI pour produire le même résultat une deuxième fois.
En résumé
Cette condition de cache, une vingtaine de lignes de configuration YAML supplémentaires, a réduit le temps moyen du pipeline de l’agence de plusieurs minutes sur la majorité des exécutions, celles où le Dockerfile n’a pas bougé. Le seul point de vigilance réel consiste à englober dans le calcul de hash tous les fichiers qui influencent réellement le contenu de l’image, pas uniquement le Dockerfile lui-même.