← Tous les articles
Jeu. 23 juillet 2026 · 5 min de lecture

🐳 Dockerfile : multi-stage, cache et secrets pour des images rapides et sĂ»res

Dockerfile multi-stage cache secrets

📚 Introduction

Un Dockerfile qui « marche » est facile Ă  Ă©crire. Un Dockerfile qui construit vite, produit une image lĂ©gĂšre et ne fuit aucun secret, c’est un autre mĂ©tier. Or c’est exactement lĂ  que se joue le confort au quotidien : un build de trois minutes au lieu de trente secondes, une image de 1,2 Go au lieu de 80 Mo, ou un token d’API qui traĂźne dans une couche publique — tout ça se dĂ©cide dans le Dockerfile.

Cet article part du principe que les bases sont acquises (si ce n’est pas le cas, commencez par les fondamentaux de Docker) et se concentre sur les quatre techniques qui font la diffĂ©rence : l’ordre des couches pour le cache, le build multi-stage, les cache mounts BuildKit et les secrets de build. Toutes reposent sur des fonctionnalitĂ©s stables et documentĂ©es — rien d’exotique.

đŸ§± L’ordre des couches n’est pas anodin

Chaque instruction d’un Dockerfile crĂ©e une couche, et Docker met ces couches en cache. Tant qu’une instruction et ses fichiers d’entrĂ©e ne changent pas, Docker rĂ©utilise la couche au lieu de la reconstruire. Mais dĂšs qu’une couche est invalidĂ©e, toutes les suivantes le sont aussi.

D’oĂč la rĂšgle d’or : ordonner du moins changeant au plus changeant. L’erreur classique est de copier tout le code avant d’installer les dĂ©pendances :

# ❌ Ă  Ă©viter : le moindre changement de code rĂ©installe tout
COPY . .
RUN npm ci

En copiant d’abord le manifeste de dĂ©pendances, on ne rĂ©installe que lorsqu’il change rĂ©ellement :

# ✅ le cache de npm ci survit aux changements de code
COPY package*.json ./
RUN npm ci
COPY . .

ComplĂ©ment indispensable : un fichier .dockerignore. Sans lui, COPY . . embarque node_modules, .git, les fichiers d’environnement et les artefacts locaux dans le contexte de build — ce qui gonfle l’image et invalide le cache pour rien.

🔎 Comment fonctionne le cache de build Docker ?

En une phrase : Docker calcule une empreinte de chaque instruction (la commande et le contenu des fichiers qu’elle copie), et si cette empreinte a dĂ©jĂ  Ă©tĂ© construite, il rĂ©utilise la couche existante au lieu de l’exĂ©cuter.

ConcrĂštement, la cascade d’invalidation explique tout : si vous modifiez une ligne Ă  l’étape 3, les Ă©tapes 1 et 2 restent en cache, mais 3, 4, 5
 sont rejouĂ©es. C’est pourquoi placer les instructions lentes et stables (installation de paquets systĂšme, dĂ©pendances) en haut, et les instructions volatiles (copie du code applicatif) en bas, change radicalement le temps de build au quotidien.

đŸ§© Multi-stage : sĂ©parer le build du runtime

C’est la technique la plus rentable pour allĂ©ger une image. Le principe : une premiĂšre Ă©tape contient tout l’outillage de compilation ; une seconde, minimale, ne reçoit que le produit fini.

# Étape de build : compilateurs, dĂ©pendances de dev, etc.
FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
 
# Image finale : rien que le nécessaire pour exécuter
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

Le COPY --from=build va chercher uniquement les fichiers utiles dans l’étape prĂ©cĂ©dente. L’image livrĂ©e ne contient ni le code source, ni les dĂ©pendances de dĂ©veloppement, ni la chaĂźne de compilation. On passe couramment de plusieurs centaines de Mo Ă  quelques dizaines. J’ai dĂ©taillĂ© une application concrĂšte de ce pattern dans le multi-stage build pour Laravel.

⚡ Cache mounts BuildKit : accĂ©lĂ©rer les dĂ©pendances

L’ordre des couches met en cache le rĂ©sultat d’un npm ci ou d’un composer install — mais dĂšs que le manifeste change, tout est retĂ©lĂ©chargĂ©. Les cache mounts de BuildKit rĂ©solvent ça : ils montent un cache persistant qui survit d’un build Ă  l’autre, sans ĂȘtre inclus dans l’image.

# syntax=docker/dockerfile:1
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci

Ici, le cache de npm (/root/.npm) est conservĂ© entre les builds. MĂȘme quand npm ci doit se relancer, il retrouve les paquets dĂ©jĂ  tĂ©lĂ©chargĂ©s au lieu de tout retirer du rĂ©seau. Sur des dĂ©pendances lourdes, le gain se compte en minutes.

La directive # syntax=docker/dockerfile:1 en premiÚre ligne est obligatoire pour débloquer la syntaxe --mount. Elle active le frontend Dockerfile de BuildKit, qui est le moteur de build par défaut de Docker moderne.

🔒 Secrets de build : jamais dans une couche

Voici l’erreur la plus dangereuse. Pour rĂ©cupĂ©rer une dĂ©pendance privĂ©e pendant le build, la tentation est de passer un token via ARG ou ENV :

# ❌ le token finit inscrit dans l'image, lisible via `docker history`
ARG NPM_TOKEN
RUN npm ci

Le problĂšme : tout ce qui passe par ARG ou ENV est gravĂ© dans les couches de l’image et ressort avec docker history. Le token est exposĂ© Ă  quiconque rĂ©cupĂšre l’image.

La bonne mĂ©thode, ce sont les secrets de build BuildKit. Le secret est montĂ© le temps d’une instruction RUN, dans un fichier sous /run/secrets/, et n’est jamais persistĂ© :

# syntax=docker/dockerfile:1
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npmtoken \
    NPM_TOKEN="$(cat /run/secrets/npmtoken)" npm ci

Et Ă  la construction, on fournit le secret depuis un fichier ou une variable :

docker build --secret id=npmtoken,src=./npm_token.txt .

Le secret est disponible pendant le RUN, puis disparaĂźt. Aucune trace dans les couches, aucune fuite via docker history. En CI, on branche ce mĂ©canisme sur le coffre Ă  secrets du pipeline — j’en parle cĂŽtĂ© GitHub Actions.

⚠ Quelques prĂ©cautions

  • Toujours un .dockerignore. Sans lui, le cache et la taille d’image partent en fumĂ©e, et des fichiers sensibles peuvent se retrouver dans le contexte de build.
  • Épinglez vos images de base. node:22-alpine reste prĂ©visible ; node:latest change sous vos pieds et casse le cache de façon imprĂ©visible.
  • Un secret n’est pas un ARG. RĂ©pĂ©tons-le : ARG/ENV pour un token est une fuite. Utilisez --mount=type=secret.
  • Attention au sharing des cache mounts. Pour des gestionnaires sensibles aux accĂšs concurrents (apt, par exemple), ajoutez sharing=locked afin d’éviter les corruptions lors de builds parallĂšles.

🎉 Conclusion

Un bon Dockerfile n’est pas qu’une liste d’instructions qui aboutit : c’est un artefact qu’on optimise. Ordonner les couches du stable au volatile pour exploiter le cache, sĂ©parer build et runtime en multi-stage pour allĂ©ger l’image, monter des cache mounts BuildKit pour accĂ©lĂ©rer les dĂ©pendances, et passer les secrets via --mount=type=secret pour ne rien laisser fuiter — ces quatre rĂ©flexes transforment l’expĂ©rience de build.

Aucun ne demande d’outil supplĂ©mentaire : tout est dans BuildKit, activĂ© par dĂ©faut. Le seul prĂ©requis, c’est la ligne # syntax=docker/dockerfile:1 en tĂȘte de fichier. À partir de lĂ , vos images sont plus rapides Ă  construire, plus lĂ©gĂšres Ă  dĂ©ployer et plus sĂ»res Ă  distribuer.

🔗 Liens utiles

/faq

Questions fréquentes

Comment réduire la taille d'une image Docker ?

+

La technique la plus efficace est le build multi-stage : on compile l'application dans une premiÚre étape avec tous les outils de build, puis on copie uniquement le résultat dans une image finale minimale. L'image livrée ne contient ni compilateur, ni dépendances de développement, ni code source superflu.

Pourquoi ne faut-il pas mettre un secret dans un ARG ou un ENV Docker ?

+

Parce que les valeurs passées via ARG ou ENV sont inscrites dans les couches de l'image et visibles avec « docker history ». Un token ou un mot de passe y reste lisible par quiconque récupÚre l'image. Il faut utiliser les secrets de build BuildKit (RUN --mount=type=secret), qui ne sont jamais persistés dans une couche.

Comment accélérer un build Docker avec le cache ?

+

Deux leviers : ordonner les instructions du moins au plus changeant (copier le manifeste de dépendances avant le code source) pour maximiser les couches réutilisées, et utiliser les cache mounts BuildKit (RUN --mount=type=cache) pour conserver les caches de gestionnaires de paquets entre deux builds.