Introduction
Le cache de construction de Docker est l'une des fonctionnalités les plus puissantes pour accélérer la création d'images, mais c'est aussi l'une des plus mal comprises. Lorsqu'il fonctionne, les développeurs gagnent des minutes à chaque build. Lorsqu'il échoue silencieusement, chaque build repart de zéro et les pipelines CI deviennent extrêmement lents. Ce guide s'adresse aux développeurs, consultants DevOps et équipes techniques de startups qui veulent arrêter de deviner et commencer à vérifier exactement comment le cache de construction se comporte dans leur propre environnement.
Nous mettrons l'accent sur la sécurité opérationnelle : observer avant de changer, limiter le rayon d'impact, utiliser des valeurs de remplacement plutôt que des secrets, vérifier chaque résultat et documenter comment récupérer lorsqu'un état attendu n'est pas atteint. Vous apprendrez les commandes de base pour inspecter et manipuler le cache de construction, verrez des exemples pratiques avec les sorties attendues et obtiendrez une antisèche des vérifications les plus utiles. Nous aborderons également des concepts connexes tels que BuildKit, la structure du Dockerfile et la mise en cache des couches, mais uniquement là où ils affectent directement le comportement du cache, le dépannage ou la récupération.
À la fin de cet article, vous serez en mesure d'exécuter un petit test local proche de la production, d'interpréter ses résultats et d'appliquer les mêmes commandes en toute confiance dans un environnement d'équipe. Tous les exemples utilisent des commandes CLI Docker standard qui fonctionnent sous Linux, macOS et Windows avec Docker Desktop.
Inventaire de version et d'environnement
Avant d'exécuter toute commande liée au cache, confirmez la version de Docker et de BuildKit que vous utilisez. Le comportement du cache a changé de manière significative entre le constructeur hérité et BuildKit, et certains indicateurs ou sorties diffèrent selon les versions. Exécutez les commandes en lecture seule suivantes pour collecter l'état actuel :
docker version --format '{{.Server.Version}}'
docker buildx version
Exemple de sortie sur une installation Docker Desktop typique :
24.0.7
github.com/docker/buildx v0.12.1
Vérifiez également quel constructeur est actif. BuildKit a été activé par défaut dans Docker 23.0 et versions ultérieures, mais il vaut la peine de le confirmer :
docker buildx ls
La sortie attendue inclut une ligne avec default * indiquant le constructeur sélectionné. Si vous voyez encore legacy ou aucune astérisque à côté du constructeur par défaut, envisagez d'exécuter docker buildx install pour définir BuildKit comme constructeur standard.
Pour les projets Compose, utilisez docker compose version et docker compose config --images pour répertorier les images référencées par vos services. Cela vous donne un inventaire rapide avant de toucher à toute commande de build.
Stockage des données et emplacement du cache
Le cache de construction est stocké dans le répertoire de données du démon Docker, généralement sous /var/lib/docker sur Linux et à l'intérieur de la VM Docker Desktop sur macOS/Windows. Si vous utilisez le constructeur hérité, le cache est géré dans le système de fichiers overlay Docker ; avec BuildKit, le cache est stocké séparément et peut même être exporté vers un registre distant. Vous pouvez inspecter l'utilisation du disque avec :
docker system df
Exemple de sortie :
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 12 2 3.45GB 1.87GB (54%)
Containers 3 1 45MB 0B (0%)
Local Volumes 5 2 2.1GB 900MB (42%)
Build Cache 48 0 5.2GB 5.2GB (100%)
Notez la ligne Build Cache. Si la taille récupérable est importante et que vous n'avez plus jamais besoin de ces couches intermédiaires, vous pouvez l'effacer avec docker builder prune, mais attendez de comprendre ce que vous pourriez perdre. Nous couvrirons l'élagage en toute sécurité plus tard.
Pour les données à l'intérieur des conteneurs, confirmez où les fichiers sont stockés avant de modifier les conteneurs. Un volume nommé tel que app_data:/var/lib/app est géré par Docker et survit à la recréation du conteneur. Un montage bind tel que ./data:/var/lib/app mappe directement vers un répertoire hôte et est plus facile à sauvegarder mais peut causer des problèmes de permissions ou de portabilité entre machines. Un test de redémarrage est le moyen le plus rapide de vérifier la persistance : arrêtez le conteneur, recréez-le et vérifiez si les fichiers attendus sont toujours présents. S'ils disparaissent, le service écrivait probablement dans le système de fichiers éphémère du conteneur au lieu du volume monté.
Chemin de configuration sûr
Lorsque vous modifiez les paramètres du cache de construction, faites-le dans la plus petite étape sûre. Le changement de configuration le plus courant est le passage entre le constructeur hérité et BuildKit. Depuis Docker 23, BuildKit est par défaut, mais de nombreuses équipes ont encore DOCKER_BUILDKIT=0 dans leur environnement pour des raisons historiques. Définissez la variable d'environnement par commande plutôt que globalement :
# Build BuildKit unique
DOCKER_BUILDKIT=1 docker build -t myapp:test .
# Retour au constructeur hérité pour cette commande uniquement
DOCKER_BUILDKIT=0 docker build -t myapp:test-legacy .
Pour faire de BuildKit le défaut pour toutes les sessions shell futures, ajoutez export DOCKER_BUILDKIT=1 à votre profil shell, mais sachez que certains anciens Dockerfiles avec une syntaxe inhabituelle ou certains plugins peuvent se comporter différemment. Vérifiez quel constructeur est actif après le changement :
docker buildx version && docker buildx ls
Si vous utilisez le constructeur hérité, le cache est stocké à l'intérieur du démon Docker et ne peut pas être facilement exporté. Avec BuildKit, vous pouvez spécifier un emplacement de stockage du cache à l'aide des indicateurs --cache-from et --cache-to, ce qui est particulièrement utile en CI. Exemple pour extraire et pousser le cache vers un registre :
docker buildx build --cache-from type=registry,ref=myregistry/myapp:cache --cache-to type=registry,ref=myregistry/myapp:cache,mode=max -t myapp:latest .
Cela indique à BuildKit de rechercher les couches de cache dans l'image de registre myapp:cache avant de construire, et de pousser de nouvelles couches de cache après un build réussi. Le mode=max inclut toutes les couches intermédiaires, pas seulement l'image finale. Pour le développement local, une approche plus simple consiste à s'appuyer sur le cache local et à éviter --no-cache sauf si vous avez réellement besoin d'un build entièrement reproductible.
Options du fichier de configuration
Si vous préférez une approche déclarative, vous pouvez définir les options du constructeur dans /etc/docker/daemon.json (Linux) ou les paramètres de Docker Desktop. Par exemple, pour activer BuildKit et définir une taille de cache maximale, ajoutez :
{
"features": {
"buildkit": true
},
"builder": {
"gc": {
"enabled": true,
"defaultKeepStorage": "10GB"
}
}
}
Après avoir modifié daemon.json, redémarrez le démon Docker (sudo systemctl restart docker sur Linux) et confirmez avec docker info | grep -i buildkit. Pour Docker Desktop, vous pouvez modifier ces paramètres dans l'interface graphique sous Préférences > Docker Engine, puis cliquez sur Appliquer et redémarrer.
Documentez toujours le changement exact et comment le rétablir. Une bonne pratique consiste à conserver les fichiers de configuration dans le contrôle de version et à utiliser une pull request pour tout changement affectant l'infrastructure de build partagée.
Vérification et diagnostics
Maintenant que vous connaissez votre environnement, vérifions comment le cache fonctionne réellement. Le moyen le plus direct est d'exécuter un build deux fois et d'observer la sortie. Créez un Dockerfile simple :
FROM alpine:3.19
RUN apk add --no-cache curl
COPY app.sh /app.sh
RUN chmod +x /app.sh
CMD ["/app.sh"]
Exécutez le build deux fois et comparez le temps et les hits de cache :
time docker build -t myapp:test .
time docker build -t myapp:test .
Lors du deuxième build, avec un cache chaud, vous devriez voir des lignes comme :
=> CACHED [1/4] FROM docker.io/library/alpine:3.19
=> CACHED [2/4] RUN apk add --no-cache curl
=> CACHED [3/4] COPY app.sh /app.sh
=> CACHED [4/4] RUN chmod +x /app.sh
=> exporting to image
=> => exporting layers
=> => writing image sha256:...
Le marqueur CACHED confirme que la couche a été réutilisée. Si vous ne voyez pas CACHED, le cache a échoué pour cette couche, ce qui signifie souvent que les entrées de la couche ont changé ou qu'il y a un problème avec le mécanisme de cache.
Inspection détaillée du cache avec BuildKit
BuildKit fournit une vue plus détaillée de l'utilisation du cache. Utilisez docker buildx build --progress=plain pour voir le journal complet, y compris les décisions de cache. Pour un regard encore plus approfondi, vous pouvez utiliser docker buildx du pour voir l'utilisation du disque par type de cache :
docker buildx du
La sortie peut ressembler à :
Total: 2.08GB
Reclaimable: 1.05GB
Shared: 300MB
Private: 1.78GB
Pour voir quelles lignes du Dockerfile causent des échecs de cache, reconstruisez avec un petit changement et observez le journal. Par exemple, si vous modifiez app.sh, seules les trois dernières couches sont reconstruites ; la couche RUN apk add reste en cache. Si la couche RUN apk se reconstruit même lorsque vous n'avez pas modifié cette ligne, alors quelque chose en amont dans le Dockerfile ou le contexte de build a changé, comme les horodatages de fichiers dans le contexte de build.
Pour voir la clé de cache exacte pour chaque instruction, exécutez avec une sortie verbeuse :
docker buildx build --progress=plain --no-cache=false -t myapp:test . 2>&1 | grep -A3 'RUN apk'
--no-cache=false est la valeur par défaut mais explicite. Recherchez les lignes CacheKey dans la sortie. Elles montrent le hachage des entrées utilisées pour rechercher le cache. Si le hachage change, le cache échoue.
Utilisation de docker image history
Pour le constructeur hérité, vous pouvez inspecter les couches d'une image existante pour voir si une couche particulière est susceptible d'être réutilisée. Exécutez :
docker image history myapp:test
Exemple de sortie :
IMAGE CREATED CREATED BY SIZE COMMENT
0123456789ab 2 minutes ago CMD ["/app.sh"] 0B buildkit.dockerfile.v0
<missing> 2 minutes ago RUN /bin/sh -c chmod +x /app.sh # buildkit 33B buildkit.dockerfile.v0
<missing> 2 minutes ago COPY app.sh /app.sh # buildkit 45B buildkit.dockerfile.v0
<missing> 2 minutes ago RUN /bin/sh -c apk add --no-cache curl # bui… 5.5MB buildkit.dockerfile.v0
<missing> 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ADD file:... in / 7.05MB
Les couches avec <missing> dans la colonne IMAGE sont des couches intermédiaires qui n'ont pas été étiquetées mais existent toujours dans le cache. Cet historique vous aide à comprendre l'ordre et la taille de chaque couche.
Vérification du cache dans les environnements distants
Si vous utilisez un constructeur distant ou un service CI, vous devrez peut-être utiliser docker buildx ls pour répertorier les constructeurs et docker buildx use <builder> pour changer de contexte. Pour vérifier si un registre distant contient une image de cache, utilisez :
docker manifest inspect myregistry/myapp:cache >/dev/null && echo "Cache image exists" || echo "No cache image"
Si l'image de cache n'existe pas, le premier build la peuplera via --cache-to. Les builds suivants devraient utiliser --cache-from pour la récupérer. Vérifiez toujours que les informations d'identification pour le registre sont disponibles dans l'environnement de build, généralement via docker login ou des secrets CI.
Modes de défaillance et récupération
Les échecs de cache ne sont pas toujours bénins. Ils peuvent faire échouer les builds, produire des résultats inattendus ou consommer silencieusement de grandes quantités d'espace disque. Voici les modes de défaillance les plus courants et comment s'en remettre.
Échec de cache dû aux changements du contexte de build
L'ensemble du contexte de build est haché pour déterminer si une instruction COPY ou ADD a changé. Si vous faites COPY . /app, alors tout changement dans n'importe quel fichier du répertoire courant (y compris .git, les fichiers temporaires ou un gros node_modules) invalidera le cache pour cette instruction et tout ce qui suit. C'est la raison numéro un pour laquelle le cache semble « ne jamais fonctionner ».
Récupération :
- Utilisez un fichier
.dockerignorepour exclure les fichiers inutiles. Exemple :
.git
node_modules
*.log
tmp/
- Soyez précis dans les commandes
COPY. Au lieu deCOPY . /app, copiez uniquement les fichiers nécessaires :COPY package.json /app/package.json, puisRUN npm install, puisCOPY src /app/src. De cette façon, les changements danssrcn'invalident pas la couche d'installation npm.
Échec de cache dû aux changements d'horodatage ou de métadonnées
Même si le contenu des fichiers est identique, des horodatages ou permissions modifiés peuvent provoquer un échec de cache car la clé de cache de couche inclut les métadonnées. Cela se produit souvent lorsque des fichiers sont extraits du contrôle de version avec des horodatages actuels ou lorsqu'un fichier est copié depuis un système de fichiers différent.
Récupération :
- Utilisez
--no-cacheuniquement pour l'instruction problématique ? Pas directement ; à la place, identifiez la source des changements de métadonnées. Pour les extractions Git, utilisezgit checkout-index -a -fqui définit les horodatages, ou utilisez un système de build qui normalise les métadonnées des fichiers avant le build. - Dans BuildKit, vous pouvez utiliser
RUN --mount=type=cache,target=/root/.cachepour monter un volume de cache persistant pour des commandes commeapt-get,pipounpm. Ce cache est indépendant des changements de fichiers dans le contexte de build, donc il persiste même lorsque d'autres couches sont reconstruites.
Corruption du cache ou état incohérent
Parfois, le cache lui-même peut devenir corrompu ou contenir des couches obsolètes de builds précédents de différentes branches ou configurations. Cela peut conduire à des builds non déterministes ou à des erreurs comme failed to compute cache key ou unknown blob.
Récupération :
- Exécutez
docker builder prunepour supprimer tout le cache de construction. Attention : cela supprime tout le cache pour tous les projets, pas seulement le projet actuel. Une approche plus sûre consiste à élaguer uniquement le cache inutilisé avecdocker builder prune --filter until=24hpour supprimer le cache plus ancien que 24 heures, ou utilisezdocker buildx prune --filter=label=project=myappsi vous étiquetez vos builds. - Si la corruption persiste, redémarrez le démon Docker. Sur Linux,
sudo systemctl restart docker. Sur Docker Desktop, utilisez l'option Redémarrer dans le menu. Après le redémarrage, exécutez un build frais avec--no-cacheune fois pour reconstruire à partir de zéro, puis les builds suivants devraient se mettre en cache normalement.
Manque d'espace disque
Le cache de construction peut consommer des dizaines de gigaoctets, surtout avec plusieurs projets et des reconstructions fréquentes. Lorsque le disque se remplit, Docker peut ne pas créer de nouvelles couches, les conteneurs peuvent ne pas démarrer et les builds échoueront avec no space left on device.
Récupération :
- Vérifiez l'utilisation du disque avec
df -hpour le répertoire de données Docker. - Exécutez
docker system dfpour voir combien d'espace est récupérable. - Utilisez
docker builder pruneavec un filtre de taille :docker builder prune --keep-storage=10GBpour conserver au maximum 10 Go de cache. - Pour un nettoyage plus agressif,
docker system prune -asupprime toutes les images, conteneurs et caches inutilisés, mais forcera chaque image à être reconstruite ou retirée. Planifiez un élagage régulier ou utilisez une tâche cron pour éviter les surprises.
Volumes de cache dédiés pour les gestionnaires de dépendances
Un point douloureux fréquent est le cache pour les gestionnaires de paquets comme npm, pip ou Maven. Par défaut, ces caches sont à l'intérieur de la couche qui exécute npm install ou pip install, donc tout changement dans package.json ou requirements.txt invalide tout le cache. L'utilisation d'un montage de cache dédié préserve le cache des paquets entre les builds même lorsque les dépendances changent :
# syntax=docker/dockerfile:1.4
FROM python:3.12
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
Ce montage garde le cache pip dans un emplacement séparé géré par BuildKit. Lorsque vous modifiez requirements.txt, seule l'étape pip install s'exécute, mais elle peut réutiliser les roues téléchargées depuis le cache, réduisant considérablement le temps d'installation. Des montages similaires existent pour apt, yum, go, maven et d'autres. Vérifiez toujours la version de syntaxe en haut de votre Dockerfile ; la fonctionnalité RUN --mount nécessite BuildKit et un frontend Dockerfile récent.
Liste de contrôle des opérations
Utilisez cette liste de contrôle avant et après chaque build pour garantir que le comportement du cache est compris et cohérent.
Vérifications avant build :
- [ ] Confirmer la version Docker et le constructeur actif :
docker versionetdocker buildx ls. Responsable : celui qui exécute le build (par exemple, développeur ou administrateur CI). Revoir lors de la mise à niveau de Docker. - [ ] S'assurer que
.dockerignoreest présent et exclut les fichiers inutiles. Responsable : développeur responsable du Dockerfile. Revoir chaque fois que le contexte de build change. - [ ] Vérifier l'espace disque :
df -hsur le répertoire de données Docker. Responsable : personne gérant la machine de build ou le runner CI. Vérifier avant chaque gros build ou hebdomadairement. - [ ] Vérifier que les informations d'identification de registre nécessaires pour
--cache-fromou--cache-tosont disponibles. Responsable : administrateur de l'infrastructure CI. Vérifier avant la première utilisation en CI.
Pendant le build :
- [ ] Exécuter le build avec progress plain pour capturer les journaux complets :
docker buildx build --progress=plain -t myapp:test . - [ ] Noter quelles couches affichent
CACHEDet lesquelles affichentRUN. Responsable : l'opérateur du build. Si des échecs inattendus se produisent, diagnostiquer immédiatement. - [ ] Si vous utilisez un cache distant, s'assurer que l'image de cache est extraite (
--cache-from) et poussée (--cache-to). Responsable : concepteur du pipeline CI. Confirmer après chaque build.
Vérifications après build :
- [ ] Vérifier la taille de l'image et le nombre de couches :
docker image ls myapp:testetdocker image history myapp:test. Responsable : développeur. Revoir après tout changement significatif du Dockerfile. - [ ] Tester que l'image s'exécute correctement :
docker run --rm myapp:test. Responsable : QA ou développeur. Faire cela avant de fusionner le changement du Dockerfile. - [ ] Documenter tout problème ou solution de contournement lié au cache dans le README du projet ou le wiki de l'équipe. Responsable : chef d'équipe ou mainteneur désigné. Mettre à jour mensuellement ou après chaque incident.
Maintenance périodique :
- [ ] Exécuter
docker system dfetdocker buildx dupour surveiller la taille du cache. Responsable : ingénieur DevOps ou gestionnaire des ressources CI. Faire cela hebdomadairement. - [ ] Élaguer le cache inutile avec
docker builder prune --filter until=168h(une semaine). Responsable : idem. Faire cela au même intervalle hebdomadaire. - [ ] Revoir la configuration du constructeur dans
daemon.jsonou les paramètres Docker Desktop. Responsable : mainteneur de l'infrastructure. Faire cela trimestriellement ou après les mises à niveau de version Docker.
Chaque élément doit avoir un responsable nommé, pas un groupe, pour garantir la responsabilité. Par exemple, dans une petite équipe, le développeur qui possède le Dockerfile est aussi celui qui examine le comportement du cache. Dans les grandes organisations, un ingénieur plateforme peut posséder l'infrastructure du constructeur. La fréquence de révision dépend de la fréquence des changements du projet ; un projet qui évolue rapidement peut nécessiter des vérifications hebdomadaires du cache, tandis qu'un projet stable peut utiliser des révisions mensuelles.
Pièges courants et comment les éviter
Voici les erreurs récurrentes que les développeurs commettent avec le cache de construction Docker et des moyens concrets de les éviter.
Piège 1 : Utiliser docker build --no-cache comme raccourci
Pourquoi cela arrive : Lorsqu'un build échoue, les développeurs ajoutent souvent --no-cache pour éviter les problèmes liés au cache. Cela tue tout le cache et rend chaque build ultérieur lent.
Comment éviter : N'utilisez --no-cache que lorsque vous avez réellement besoin d'un build propre (par exemple, pour des builds reproductibles ou après une mise à niveau de la chaîne d'outils). Sinon, essayez de corriger la cause réelle de l'échec du cache. Si vous devez contourner le cache pour une seule instruction, envisagez d'utiliser RUN --mount=type=cache pour cette instruction ou d'invalider uniquement la couche nécessaire avec un changement de commentaire.
Piège 2 : Copier tout le contexte de build
Pourquoi cela arrive : COPY . /app est facile, mais inclut tout dans le répertoire, souvent de gros fichiers et répertoires qui changent fréquemment.
Comment éviter : Utilisez un fichier .dockerignore et soyez spécifique dans les commandes COPY. Divisez les opérations COPY pour éviter d'invalider toute la chaîne de couches.
Piège 3 : Ne pas utiliser les montages de cache de BuildKit
Pourquoi cela arrive : De nombreux développeurs ignorent les montages de cache ou pensent qu'ils sont complexes. Sans eux, les caches des gestionnaires de paquets sont perdus à chaque changement de dépendance, provoquant des builds lents.
Comment éviter : Utilisez la syntaxe RUN --mount=type=cache. Elle est prise en charge dans les versions modernes de Docker et accélère considérablement l'installation des dépendances. Reportez-vous à la documentation Docker pour des exemples pour npm, pip, apt, etc.
Piège 4 : Mal comprendre la mise en cache des couches avec ARG et ENV
Pourquoi cela arrive : Changer un argument de construction (ARG) ou une variable d'environnement (ENV) utilisé dans une couche précoce invalide toutes les couches suivantes. Les développeurs peuvent ne pas réaliser que ces variables font partie de la clé de cache.
Comment éviter : Placez ARG et ENV aussi tard que possible dans le Dockerfile, idéalement juste avant l'instruction qui les utilise. Si un argument n'est utilisé que pour des métadonnées, envisagez d'utiliser LABEL à la place.
Piège 5 : Ignorer le cache dans les pipelines CI
Pourquoi cela arrive : De nombreux systèmes CI démarrent avec un environnement propre, donc le cache Docker est vide à chaque build. Sans cache distant, les builds sont aussi lents qu'un build sans cache.
Comment éviter : Utilisez --cache-from et --cache-to avec un registre. En CI, incluez ces indicateurs dans l'étape de build. Pour GitHub Actions, utilisez une action dédiée comme docker/build-push-action qui prend en charge les backends de cache. Pour GitLab CI, utilisez cache avec key et paths pour persister le cache de couches Docker, ou utilisez --cache-from avec l'image précédente.
Piège 6 : Négliger l'espace disque
Pourquoi cela arrive : Le cache de construction s'accumule silencieusement, et les développeurs vérifient rarement l'utilisation du disque jusqu'à ce que le build échoue.
Comment éviter : Mettez en place un élagage périodique dans le cadre de la maintenance de routine. Définissez des alertes sur l'utilisation du disque pour les machines de build. Utilisez docker builder prune --keep-storage pour limiter la taille du cache.
Conclusion
Les commandes du cache de construction Docker ne sont utiles que si chaque recommandation est versionnée, observable et réversible là où la technologie le permet. Copier une commande sans vérifier les prérequis et la sortie attendue n'est pas une procédure opérationnelle ; c'est un pari. Tout au long de ce guide, nous avons souligné l'importance de voir l'état actuel, de changer une chose à la fois et de vérifier le résultat avec des commandes concrètes.
Comme prochaine étape, choisissez une vérification à faible risque pour votre propre projet : exécutez un build deux fois avec progress plain, notez quelles couches sont mises en cache, puis modifiez délibérément un fichier et observez quelles couches se reconstruisent. Enregistrez l'état avant et après, et partagez les résultats avec votre équipe. Cette petite expérience renforcera votre intuition bien mieux que de lire de la documentation plus abstraite.
Un flux de travail technique fiable rend les échecs visibles, protège les valeurs sensibles, limite les changements à la ressource prévue et définit la vérification de récupération avant qu'un incident ne force la décision. Appliquez cet état d'esprit au cache de construction Docker, et vous passerez moins de temps à attendre les builds et plus de temps à livrer des fonctionnalités.