Changer le thème

Accélérer les builds Docker : guide pratique du cache pour des builds 10× plus rapides

Easton editorial illustration: registry transfer crane

Vous corrigez une typo, relancez docker build — et npm install repart. Dix minutes passées, vingt stories scrollées, deux cafés bus, la barre de progression tourne encore.

Ceux qui font du développement conteneurisé connaissent cette frustration.

Étudier le mécanisme de cache Docker pour ramener le build de 10 minutes à 30 secondes.

30 s
Temps de build
De 10 minutes à 30 secondes, gain ×20

Cet article présente 3 astuces immédiates : configurer .dockerignore, comprendre le cache par couches, optimiser l’ordre des instructions Dockerfile. Et en bonus, le montage de cache BuildKit. Votre build est lent ? Continuez la lecture.

Pourquoi votre build Docker est-il si lent ?

Le contexte de build est trop volumineux

Commençons par un piège fréquent : le contexte de build (Build Context).

Quand vous exécutez docker build ., Docker ne lance pas d’abord le Dockerfile — il empaquete tous les fichiers du répertoire . et les envoie au daemon. Tous. node_modules, .git, centaines de Mo de données de test.

Le pire que j’aie vu : un projet frontend avec 800 Mo de contexte. La transmission seule prend 2-3 minutes. Alors que l’image n’a besoin que de moins de 10 Mo de code source.

C’est comme expédier un livre en emballant toute l’étagère.

L’effet domino de l’invalidation du cache par couches

Deuxième problème : ne pas comprendre le cache par couches de Docker.

Les images Docker sont stratifiées. Chaque instruction du Dockerfile — FROM, RUN, COPY — crée une couche. À chaque build, Docker vérifie si une couche peut être réutilisée. Instruction identique, fichiers inchangés = cache, pas de réexécution.

Ça a l’air parfait, non ?

Mais dès qu’une couche est invalidée, toutes les suivantes doivent être reconstruites. Comme des dominos : le premier tombe, le reste suit.

Beaucoup de Dockerfiles ressemblent à ceci :

FROM node:18
COPY . /app
WORKDIR /app
RUN npm install

Rien d’anormal en apparence ? En réalité, gros problème.

La ligne COPY . /app copie tout le projet. Vous modifiez un seul fichier — même une typo dans README.md — et cette couche est invalidée. Résultat : npm install doit tourner à nouveau.

Voilà pourquoi une ligne de code change relance toute l’arborescence des dépendances.

Ordre des instructions mal pensé

Troisième piège : ne pas savoir comment ordonner les instructions.

La stratégie de cache Docker est simple : vérification de haut en bas, arrêt dès qu’une couche échoue. Donc : instructions stables en premier, instructions changeantes en dernier.

Pourtant, beaucoup de Dockerfiles font l’inverse : copier le code d’abord (change souvent), installer les dépendances ensuite (change rarement). Chaque modification de code invalide le cache des dépendances.

En clair : on n’a pas identifié ce qui change souvent et ce qui reste stable.

Astuce 1 — Configurer .dockerignore pour réduire le contexte de build

Problèmes posés. Commençons par l’optimisation la plus simple et la plus visible : .dockerignore.

C’est quoi exactement ?

Vous connaissez .gitignore ? .dockerignore fonctionne pareil : il indique à Docker quels fichiers ne pas inclure dans le contexte de build.

Création ultra simple : à la racine du projet (même niveau que le Dockerfile), créez un fichier .dockerignore et ajoutez vos règles.

Configuration pour un projet Node.js ?

Voici la config que j’utilise :

# Répertoires de dépendances
**/node_modules/
**/npm-debug.log
**/.npm

# Git
.git/
.gitignore
.gitattributes

# Tests et documentation
**/test/
**/tests/
**/docs/
**/*.md
!README.md

# IDE et éditeurs
.vscode/
.idea/
*.swp
*.swo
.DS_Store

# Variables d'environnement et config
.env
.env.*
*.local

# Artefacts de build
dist/
build/
coverage/

Quelques points clés :

  1. Excluez node_modules. Plusieurs centaines de Mo possibles, et l’image réinstalle de toute façon — inutile de copier depuis l’hôte.
  2. Utilisez le préfixe **/ pour les répertoires imbriqués. **/node_modules/ couvre ./node_modules/ et ./packages/lib/node_modules/.
  3. Ajoutez le slash final pour les répertoires. node_modules/ = répertoire, node_modules = fichier. Docker fait la distinction.

Quel impact concret ?

Test sur un projet Next.js :

  • Avant : contexte 520 Mo, transmission 2 min 15 s
  • Après : contexte 4,8 Mo, transmission 3 s

Oui, 3 secondes. Deux minutes gagnées.

Sans compter la taille d’image — plus de .git ni node_modules embarqués, l’image passe de 1,2 Go à 680 Mo.

Pièges courants

Piège 1 : .dockerignore ne s’applique qu’à la racine du contexte de build. Si vous lancez docker build -f subfolder/Dockerfile ., placez .dockerignore à la racine du projet, pas dans subfolder.

Piège 2 : node_modules sans slash peut ne pas fonctionner. Préférez node_modules/.

Piège 3 : oublier d’exclure .git. Plusieurs centaines de Mo, jamais utilisés dans l’image.

Astuce 2 — Comprendre et exploiter le cache par couches Docker

.dockerignore accélère la transmission ; le cœur du sujet reste le fonctionnement du cache.

Comment fonctionne le cache par couches ?

Une image Docker ressemble à un gâteau mille-feuilles : chaque couche correspond au résultat d’une instruction Dockerfile.

Exemple :

FROM node:18          # Couche 1
RUN apt-get update    # Couche 2
COPY package.json .   # Couche 3
RUN npm install       # Couche 4
COPY . .              # Couche 5

À chaque build, Docker vérifie couche par couche :

  1. Couche 1 : FROM — l’image node:18 existe localement ? Cache.
  2. Couche 2 : RUN — le texte de l’instruction est identique ? Cache.
  3. Couche 3 : COPY — checksum de package.json. Fichier inchangé ? Cache.
  4. Couche 4 : RUN — même logique.
  5. Couche 5 : idem.

Point crucial : dès qu’une couche est invalidée, toutes les suivantes sont reconstruites.

L’effet domino évoqué plus haut. Vous modifiez package.json à la couche 3, npm install (couche 4) et la copie du code (couche 5) doivent être rejoués.

Comment savoir si le cache est utilisé ?

Regardez la sortie du build :

Step 3/5 : COPY package.json .
 ---> Using cache
 ---> 3a8f29e7c5b1

Using cache = cache actif. Sinon, reconstruction en cours.

Vous pouvez aussi utiliser docker history <image-id> pour voir l’historique des couches, la taille (SIZE) et la date de création.

Pourquoi COPY est particulier ?

RUN ne regarde que le texte de la commande. RUN npm install — texte identique, Docker réutilise le cache.

COPY et ADD sont différents : Docker calcule le checksum du contenu copié. Même nom de fichier, contenu modifié = cache invalidé.

Logique intelligente — le contenu a changé, les étapes suivantes peuvent en être affectées.

Mais COPY . . devient dangereux : la moindre modification (même README.md) invalide la couche.

Astuce 3 — Optimiser l’ordre des instructions Dockerfile

Compris le principe ? Passons à la pratique : comment écrire un Dockerfile qui maximise le cache ?

La règle d’or : du stable au changeant

En une phrase : instructions stables en premier, instructions changeantes en dernier.

Pourquoi ? Docker vérifie de haut en bas. Si les premières couches restent stables, les modifications en bas n’impactent pas le cache du haut.

Concrètement :

  1. Image de base — quasi immuable
  2. Dépendances système — changent parfois
  3. Dépendances projet — changent occasionnellement
  4. Code source — change tous les jours

Dans cet ordre, vous maximisez la réutilisation du cache.

Mauvais exemple : copier le code avant d’installer les dépendances

Beaucoup commencent ainsi :

FROM node:18
WORKDIR /app

# Erreur : copier tout le projet d'un coup
COPY . .

# Puis installer les dépendances
RUN npm install

# Commande de démarrage
CMD ["npm", "start"]

Le problème ? Vous modifiez le code source, COPY . . invalide le cache, et npm install doit recommencer.

Résultat : une ligne de JS modifiée = réinstallation de centaines de paquets npm. Dix minutes perdues.

Bon exemple : dépendances d’abord, code ensuite

Version optimisée :

FROM node:18
WORKDIR /app

# Étape 1 : copier uniquement les fichiers de dépendances
COPY package.json package-lock.json ./

# Étape 2 : installer (cette couche sera mise en cache)
RUN npm ci --only=production

# Étape 3 : copier le code source
COPY . .

# Commande de démarrage
CMD ["npm", "start"]

Avantages :

  1. Tant que package.json ne change pas, la couche npm ci utilise le cache.
  2. Vous modifiez le code : seule COPY . . est invalidée, l’installation des dépendances reste en cache.
  3. Au deuxième build, npm install est sauté — vitesse maximale.

En test, cette réorganisation ramène les builds suivants de 7-8 minutes à environ 30 secondes.

Même logique pour d’autres langages

Projet Python :

FROM python:3.11
WORKDIR /app

# Copier requirements.txt d'abord
COPY requirements.txt .

# Puis pip install
RUN pip install --no-cache-dir -r requirements.txt

# Copier le code en dernier
COPY . .

Projet Go :

FROM golang:1.21
WORKDIR /app

# Copier go.mod et go.sum
COPY go.mod go.sum ./

# Télécharger les dépendances
RUN go mod download

# Puis copier le code
COPY . .

# Compiler
RUN go build -o main .

Même principe : séparer les fichiers de gestion des dépendances et le code source pour réutiliser au maximum le cache d’installation.

Astuce avancée : COPY granulaire

Pour des structures complexes, vous pouvez aller plus loin :

# Copier d'abord les fichiers de config stables
COPY .eslintrc.json .prettierrc ./

# Puis les fichiers de dépendances
COPY package*.json ./
RUN npm install

# Puis les bibliothèques partagées (si applicable)
COPY ./lib ./lib

# Enfin le code métier
COPY ./src ./src

Moins courant, mais utile dans certains cas (monorepo par exemple).

Astuce avancée — Montage de cache BuildKit

Les optimisations de base couvertes. Passons au montage de cache BuildKit.

C’est quoi BuildKit ?

BuildKit est le moteur de build introduit avec Docker 18.09 — plus rapide que l’ancien moteur, avec des capacités de cache avancées.

Activation simple :

# Activation temporaire
export DOCKER_BUILDKIT=1
docker build .

# Ou directement sur la commande
DOCKER_BUILDKIT=1 docker build .

Sur Docker 19.03+, BuildKit est souvent activé par défaut. En cas de doute : docker version.

Le montage de cache, concrètement ?

Le cache par couches a une limite : couche invalidée = réexécution complète.

Vous modifiez package.json, ajoutez une dépendance — la couche npm install disparaît. Tous les paquets, y compris ceux déjà téléchargés, doivent être retéléchargés.

Le montage de cache résout ce problème : même si le cache par couches échoue, le cache du gestionnaire de paquets est conservé.

En bref : un répertoire de cache persistant partagé entre les builds.

Comment l’utiliser ?

Exemple Node.js :

FROM node:18
WORKDIR /app

COPY package*.json ./

# Point clé : monter le répertoire de cache npm
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

COPY . .
CMD ["npm", "start"]

--mount=type=cache,target=/root/.npm :

  • type=cache : montage de type cache
  • target=/root/.npm : répertoire de cache npm

Même si package.json change et la couche est invalidée, npm ne retélécharge pas tout. Il lit le cache existant dans /root/.npm et ne récupère que les paquets nouveaux ou mis à jour.

Autres gestionnaires de paquets

Yarn :

RUN --mount=type=cache,target=/root/.yarn \
    yarn install --frozen-lockfile

pip (Python) :

RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

apt (paquets système) :

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    apt-get update && apt-get install -y gcc

Notez sharing=locked pour apt : apt exige un accès exclusif à son cache — ce paramètre évite les conflits en builds concurrents.

Quels résultats ?

Test sur un projet avec 200+ dépendances :

  • Cache par couches invalidé, montage de cache actif : installation de 8 minutes à 1 min 30 s
  • Démarrage à froid (aucun cache) : toujours 8 minutes

Le montage de cache est la « deuxième ligne de défense ». Cache par couches intact = le plus rapide (étape sautée). Invalidé = le montage de cache limite les retéléchargements.

Points d’attention

  1. Rétention limitée par défaut : BuildKit nettoie les caches de plus de 2 jours et dépassant 512 Mo. En CI/CD, ajustez la stratégie si nécessaire.

  2. Pas toujours nécessaire : peu de dépendances (une dizaine), le montage de cache apporte peu.

  3. Chemins à vérifier : chaque gestionnaire a son répertoire de cache — consultez la documentation.

Conclusion

Trois actions essentielles :

Immédiat : créez .dockerignore à la racine, excluez node_modules, .git, fichiers de test. Cinq minutes, contexte réduit de 90 %+.

Aujourd’hui : réorganisez l’ordre des instructions Dockerfile. COPY des dépendances, RUN install, COPY du code. Les builds suivants passent de 10 minutes à 30 secondes.

Quand vous avez le temps : montage de cache BuildKit si beaucoup de dépendances mises à jour souvent — il sauve la mise quand le cache par couches échoue.

Sur mon projet, ces trois étapes ont ramené le build de 10 minutes à 30 secondes et l’image de 1,2 Go à 680 Mo. L’un des meilleurs rapports effort/résultat que j’aie vus.

Votre build Docker est lent ? Essayez ce guide. Revenez en commentaire me dire de combien vous avez accéléré — je suis curieux.

Processus complet d’accélération des builds Docker

Passer de 10 minutes à 30 secondes : cache par couches, .dockerignore et optimisation Dockerfile

Estimated time: PT30M

  1. 1

    Step 1: Comprendre pourquoi c’est lent : contexte et cache par couches

    Problème de contexte de build :
  2. 2

    Step 2: Astuce 1 : configurer .dockerignore

    Immédiat : créez .dockerignore à la racine, excluez node_modules, .git, tests — 5 minutes, contexte réduit de 90 %+.
  3. 3

    Step 3: Astuce 2 : optimiser l’ordre des instructions Dockerfile

    Aujourd’hui : COPY des dépendances, RUN install, COPY du code — builds suivants de 10 minutes à 30 secondes.
  4. 4

    Step 4: Astuce 3 : montage de cache BuildKit

    Quand vous avez le temps : si beaucoup de dépendances mises à jour souvent, le montage de cache BuildKit sauve la mise quand le cache par couches échoue.
  5. 5

    Step 5: (Note

    sharing=locked pour apt — accès exclusif au cache, évite les conflits en builds concurrents)

FAQ

Pourquoi les builds Docker sont-ils si lents ?
Problème de contexte de build :
• Quand vous lancez docker build ., Docker commence par empaqueter tous les fichiers du répertoire courant et les envoyer au daemon
• node_modules, .git, données de test, etc.
• Un projet frontend peut atteindre 800 Mo de contexte ; la transmission seule prend 2-3 minutes
• Alors que l'image n'a besoin que de moins de 10 Mo de code source
• C'est comme expédier un livre en emballant toute l'étagère

Effet domino de l'invalidation du cache par couches :
• Les images Docker sont stratifiées ; chaque instruction (FROM, RUN, COPY) crée une couche
• Docker vérifie si chaque couche peut être réutilisée ; instruction identique et fichiers inchangés = cache
• Mais dès qu'une couche est invalidée, toutes les suivantes doivent être reconstruites — comme des dominos

Beaucoup de Dockerfiles ressemblent à : FROM node:18, COPY . /app, WORKDIR /app, RUN npm install — chaque changement de code relance npm install.
Comment configurer .dockerignore pour réduire le contexte de build ?
À faire tout de suite : créez un .dockerignore à la racine du projet, excluez node_modules, .git, fichiers de test — 5 minutes, contexte réduit de 90 %+.

Exemple .dockerignore :
• node_modules (exclure les dépendances)
• .git (exclure l'historique Git)
• *.log (exclure les logs)
• .env (exclure les variables d'environnement)
• dist (exclure les artefacts de build)
• test (exclure les tests)
• *.md (exclure la documentation)

Après configuration : contexte de 800 Mo à moins de 10 Mo, transmission de 2-3 minutes à quelques secondes.
Comment optimiser l'ordre des instructions Dockerfile ?
À faire aujourd'hui : réorganisez votre Dockerfile — COPY des fichiers de dépendances, RUN install, puis COPY du code source. Les builds suivants passent de 10 minutes à 30 secondes.

Principe :
• Instructions peu changeantes en premier (FROM, dépendances système, dépendances applicatives)
• Instructions très changeantes en dernier (COPY du code source)

Avant :
• FROM node:18
• COPY . /app
• WORKDIR /app
• RUN npm install
• Chaque changement de code relance npm install

Après :
• FROM node:18
• WORKDIR /app
• COPY package*.json ./
• RUN npm install
• COPY . .
• npm install ne se relance que si package.json change ; les changements de code n'affectent pas l'installation des dépendances
Comment utiliser le montage de cache BuildKit ?
Quand vous avez le temps : si le projet a beaucoup de dépendances mises à jour souvent, essayez le montage de cache BuildKit — il sauve la mise quand le cache par couches échoue.

Montage de cache BuildKit :
• Utilisez --mount=type=cache pour monter un répertoire de cache
• Persistez le cache npm install (node_modules) sur l'hôte
• Réutilisez le cache au build suivant, vitesse ×10 ou plus

Exemples :
• npm : RUN --mount=type=cache,target=/root/.npm npm install
• yarn : RUN --mount=type=cache,target=/root/.yarn yarn install --frozen-lockfile
• pip : RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
• apt : RUN --mount=type=cache,target=/var/cache/apt,sharing=locked apt-get update && apt-get install -y gcc

Note : sharing=locked pour apt, car apt exige un accès exclusif à son cache — évite les conflits en builds concurrents.
Quels sont les résultats de l'optimisation des builds Docker ?
Résultats :
• Temps de build de 10 minutes à 30 secondes (×20)
• Taille d'image de 1,2 Go à 680 Mo
• Contexte de build de 800 Mo à moins de 10 Mo
• Transmission de 2-3 minutes à quelques secondes

Sur mon propre projet, ces trois étapes ont ramené le build de 10 minutes à 30 secondes et l'image de 1,2 Go à 680 Mo. Honnêtement, c'est l'un des meilleurs rapports effort/résultat que j'aie vus.

Trois actions :
• Immédiat : créer .dockerignore à la racine
• Aujourd'hui : réorganiser l'ordre des instructions Dockerfile
• Quand vous avez le temps : montage de cache BuildKit si beaucoup de dépendances

10 min de lecture · Publié le: 17 déc. 2025 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog