Optimisation Dockerfile : 5 astuces pour réduire la taille de l'image de 80 %

La barre de progression du terminal est bloquée sur « Pushing to registry » depuis 30 minutes.
3,2 Go.
Première image Docker pour une app Node.js, Dockerfile copié d’un tutoriel en ligne. Le build réussit, mais le volume surprend. Le lendemain matin, un collègue sur Slack : « Ton image a emballé tout le système d’exploitation ? Mon disque est plein. »
Image de base Ubuntu ? node_modules ? Outils de compilation ? Résultat : un simple service API, image 50× plus grosse que le code du projet. Après avoir parcouru la doc Docker et les bonnes pratiques, ce monstre de 3,2 Go est passé à 180 Mo. -94 %.
Cet article détaille les 5 astuces les plus efficaces découvertes au fil du processus. Pas seulement le « comment », mais aussi le « pourquoi » — comprendre le principe vaut mieux que mémoriser des commandes.
Comprendre pourquoi une image Docker est si volumineuse
Avant les astuces, il faut saisir la racine du problème.
Une image Docker est en couches. Chaque instruction RUN, COPY, ADD crée une nouvelle couche de système de fichiers. Ces couches s’empilent pour former l’image finale. Point clé : chaque couche n’ajoute que, elle ne supprime pas.
Exemple dans un Dockerfile :
RUN apt-get update
RUN apt-get install -y build-essential
RUN rm -rf /var/lib/apt/lists/*
En surface, la dernière ligne supprime le cache apt. En réalité, ce cache est figé dans la deuxième couche. La troisième marque seulement « ces fichiers sont supprimés », mais les données restent dans l’image.
Comme des photos prises à chaque étape d’un ménage : même si vous jetez les déchets à la fin, les photos avec les déchets partent avec vous. Un peu absurde, mais c’est le mécanisme copy-on-write de Docker.
Avec docker history sur une image non optimisée, la somme des couches dépasse largement les fichiers réellement nécessaires.
Autre point souvent négligé : l’image de base fixe le plancher de volume. ubuntu:20.04 pèse déjà 72 Mo, node:16 monte à 1,09 Go — Debian complète avec des outils système que vous n’utiliserez peut-être jamais.
Une fois compris, la stratégie est claire : réduire les couches, choisir une base légère, installer et nettoyer dans la même couche.
Astuce 1 : choisir la bonne image de base
Le choix de l’image de base, c’est comme l’emplacement pour acheter un appartement. Mal choisi, le plafond est bas quoi que vous fassiez ensuite.
Quelques chiffres :
node:16→ 1,09 Gonode:16-slim→ 240 Monode:16-alpine→ 174 Moalpine:latest→ 5,6 Mo
L’écart parle de lui-même. En passant de node:16 à node:16-alpine, l’image est passée de 1,2 Go à 400 Mo sans toucher au code.
Qu’est-ce qu’Alpine Linux ?
Une distribution Linux conçue pour les conteneurs. Minimaliste : composants essentiels uniquement. musl libc au lieu de glibc, apk au lieu de apt.
Avantages :
- Volume réduit (5 Mo vs 72 Mo pour Ubuntu)
- Sécurité (surface d’attaque minimale)
- Démarrage rapide
Mais des pièges existent.
Le piège de compatibilité Alpine
Avec musl libc, certains binaires précompilés peuvent échouer. Une fois, un module natif Node.js en C++ a planté sur Alpine avec « library not found » — libc incompatible.
Conseil pragmatique :
- Tester d’abord la variante Alpine (suffixe
-alpine) - En cas de problème, passer à
-slim(Debian allégée) - Image standard en dernier recours — cas rares
Changement minimal :
# Avant
FROM node:16
# Après
FROM node:16-alpine
Une ligne, 800 Mo économisés.
Vérifier l’effet
Après le build :
docker images your-image-name
Regardez la colonne SIZE. Si c’est encore lourd, le problème va au-delà de l’image de base — continuez la lecture.
Astuce 2 : fusionner les RUN pour réduire les couches
Simple à comprendre, souvent oublié en pratique.
Chaque RUN crée une couche. Et surtout : supprimer un fichier n’est effectif que dans la même couche.
Contre-exemple :
# Mauvais (3 couches)
RUN apt-get update
RUN apt-get install -y python3 gcc
RUN rm -rf /var/lib/apt/lists/*
Le cache apt (souvent des dizaines de Mo) reste dans la deuxième couche. La suppression de la troisième ne fait que marquer l’absence, les données restent.
Bonne approche avec && :
# Bon (1 couche)
RUN apt-get update && \
apt-get install -y python3 gcc && \
rm -rf /var/lib/apt/lists/*
Installation et nettoyage dans la même couche — la suppression est réelle.
Le rôle du backslash
Le \ permet de couper une longue commande sur plusieurs lignes pour la lisibilité.
Quand fusionner ou non
Pas tous les RUN à fusionner. Règle simple :
- Fusionner : installation + nettoyage, téléchargement + décompression + suppression de l’archive
- Ne pas fusionner : opérations sans lien logique, étapes qui changent souvent (casse le cache de build)
Exemple :
# Bonne stratification
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN npm install
RUN npm run build
Dépendances système sur une couche, npm install sur une autre (package.json change souvent), build sur une troisième. Modifier package.json réutilise le cache des couches précédentes.
Mesure : un projet passé de 12 RUN à 4, image de 520 Mo à 320 Mo.
Astuce 3 : build multi-étapes, ne garder que l’essentiel
Le build multi-étapes (Multi-stage Build) est l’arme la plus efficace pour alléger une image Docker. Sans conteste.
Idée simple : séparer build et exécution.
Compiler un programme Go demande toute la chaîne Go (centaines de Mo), le binaire final peut faire 10 Mo. Emballer la chaîne dans l’image finale, c’est du gaspillage.
Le build multi-étapes résout ça : plusieurs étapes dans un Dockerfile — la première pour construire, la seconde ne copie que les artefacts.
Exemple Node.js :
# === Étape de build ===
FROM node:16-alpine AS builder
WORKDIR /app
# Copier les fichiers de dépendances
COPY package*.json ./
RUN npm install
# Copier le code source et construire
COPY . .
RUN npm run build
# === Étape d'exécution ===
FROM node:16-alpine
WORKDIR /app
# Copier uniquement le nécessaire
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Regardez COPY --from=builder : copie depuis la première étape vers la seconde. L’image finale ne contient que la deuxième étape ; tous les intermédiaires de la première sont jetés.
Quand utiliser le build multi-étapes
Scénarios typiques :
- Langages compilés : Go, Rust, C++, etc.
- Projets frontend : compilation TypeScript, bundling Webpack
- Outils de build requis : ex. gcc pour certaines libs Python
Mon projet Node.js : TypeScript → JavaScript. Sources 400 Mo (dont @types dans node_modules), dist 2 Mo. Multi-étapes : 400 Mo → 220 Mo.
Piège fréquent
Ne pas refaire npm install en phase d’exécution en pensant « il faut les dépendances ». Sinon les devDependencies s’installent aussi — gaspillage.
Bon usage : npm install en build (dev deps pour compiler), puis copier node_modules en runtime. Ou plus précis :
# Étape de build
RUN npm install
# Étape d'exécution
RUN npm install --production
Production uniquement : -30 à -40 % de volume.
Le multi-étapes peut sembler abstrait au début ; une fois compris, le design paraît élégant. Comme préparer un voyage : tout étaler à la maison (build), mais en cabine (runtime) ne prendre que l’essentiel dans la valise.
Astuce 4 : .dockerignore pour exclure l’inutile
.dockerignore ressemble à .gitignore, mais beaucoup l’oublient.
Avec COPY . . dans le Dockerfile, Docker envoie tout le répertoire au daemon comme contexte de build. node_modules de centaines de Mo, historique .git, tests, logs — tout part.
Même si ces fichiers ne servent pas au final, le build ralentit et on risque d’emballer des secrets (.env).
Solution : créer .dockerignore à la racine du projet.
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
README.md
.vscode
.idea
*.md
.DS_Store
coverage/
.pytest_cache/
__pycache__/
*.pyc
dist-local/
Principes clés
À exclure :
- Répertoires de dépendances déjà présents : node_modules, vendor, target (réinstallés au build)
- Config d’outils de dev : .vscode, .idea, .editorconfig
- Git : .git, .gitignore (souvent des dizaines de Mo)
- Documentation : README, CHANGELOG, docs/
- Informations sensibles : .env, credentials.json, *.pem
J’ai oublié .git une fois : 500 Mo d’historique à chaque build. Avec .dockerignore : build de 2 min à 30 s, image plus légère.
Astuce pratique
Si vous ne savez pas ce qui est copié, build puis inspectez le conteneur :
docker run --rm -it your-image sh
ls -lah
Fichier indésirable ? Ajoutez-le à .dockerignore.
Simple en apparence, effet immédiat — surtout en frontend où dist, node_modules et .cache dépassent facilement le Go.
Astuce 5 : nettoyer le cache des gestionnaires de paquets
npm, pip, apt, apk laissent un cache après installation. Utile en dev local, inutile dans une image Docker.
Beaucoup savent qu’il faut nettoyer, mais mal.
Nettoyer dans le même RUN que l’installation
Encore une fois : nettoyage et installation dans la même couche.
# ❌ Nettoyage inefficace
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/* # inutile
# ✅ Nettoyage efficace
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
Par gestionnaire :
Node.js (npm/yarn)
# npm - classique
RUN npm install && \
npm cache clean --force
# npm - plus simple (sans cache)
RUN npm install --no-cache
# yarn
RUN yarn install && \
yarn cache clean
Python (pip)
# Direct : pas de cache à la source
RUN pip install --no-cache-dir -r requirements.txt
# Ou nettoyage après
RUN pip install -r requirements.txt && \
rm -rf ~/.cache/pip
Alpine (apk)
# Option pratique d'apk
RUN apk add --no-cache package-name
# Ou nettoyage manuel
RUN apk add package-name && \
rm -rf /var/cache/apk/*
Debian/Ubuntu (apt)
RUN apt-get update && \
apt-get install -y package-name && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Données mesurées
Test sur un projet Python, mêmes dépendances :
- Sans nettoyage : 450 Mo
- Avec nettoyage : 320 Mo
- Avec
--no-cache-dir: 310 Mo (le plus propre)
140 Mo d’écart — un seul paramètre.
Dev vs production
En production, --production ou --no-dev pour n’installer que le nécessaire. Les devDependencies représentent souvent 30-50 % du volume.
# Node.js — production uniquement
RUN npm install --production
# Python — requirements prod séparés
RUN pip install --no-cache-dir -r requirements-prod.txt
Combinées, ces 5 astuces se multiplient, pas s’additionnent. Mon projet de 3,2 Go est tombé à 180 Mo en les appliquant toutes.
Cas complet : optimisation d’une app Node.js
Assez de théorie — voici l’évolution réelle du Dockerfile d’un service API Express.
Avant optimisation (1,2 Go)
FROM node:16
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
Direct, mais énorme.
Étape 1 : Alpine (→ 400 Mo, -67 %)
FROM node:16-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
Une ligne, 800 Mo de moins.
Étape 2 : .dockerignore (→ 380 Mo, -5 %)
Créer .dockerignore :
node_modules
.git
*.md
.env
coverage
Gain modeste en taille, build nettement plus rapide.
Étape 3 : build multi-étapes (→ 220 Mo, -42 %)
# Étape de build
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Étape d'exécution
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
L’étape la plus impactante — tous les intermédiaires de build écartés.
Étape 4 : prod + nettoyage cache (→ 180 Mo, -18 %)
# Étape de build
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Étape d'exécution
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production --no-cache && \
npm cache clean --force
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
Version finale : 180 Mo, -85 % par rapport aux 1,2 Go initiaux.
Récapitulatif de la feuille de route
1,2 Go (node:16 original)
↓ Alpine
400 Mo (-67 %)
↓ .dockerignore
380 Mo (-5 %)
↓ multi-étapes
220 Mo (-42 %)
↓ prod + nettoyage
180 Mo (-18 %)
────────────────
Total -85 %
Quelles astuces performent le mieux ?
De ce cas :
- Alpine : effet immédiat, le plus simple
- Multi-étapes : le plus fort, un peu d’apprentissage
- Nettoyage + prod : détails qui s’accumulent
Temps limité ? Priorisez les deux premières.
Conclusion
Récap des 5 astuces :
- Alpine — réduire à la source
- Fusionner les RUN — installer et nettoyer dans la même couche
- Build multi-étapes — ne garder que le runtime
- .dockerignore — exclure l’inutile et les secrets
- Nettoyage du cache — options
--no-cache
Elles se combinent. Alpine + multi-étapes règlent ~80 % ; le reste vient du nettoyage et de l’exclusion.
Passez à l’action
N’attendez pas un incident. Prenez un projet existant et testez :
- Passage à Alpine possible ? (souvent oui)
- RUN d’installation et de nettoyage séparés ? (fusionnez)
- Ajoutez un build multi-étapes (obligatoire pour les projets compilés)
- Créez .dockerignore
- Ajoutez
--no-cacheaux gestionnaires de paquets
Après le build, docker images pour mesurer l’économie.
Aller plus loin
- Cache mounts de Docker BuildKit
- Images Distroless (Google, plus petites qu’Alpine)
- Scan de sécurité (Trivy, Grype)
L’optimisation Dockerfile est un processus continu. Vérifiez la taille à chaque build — l’habitude fait le reste.
Que vos images soient de plus en plus légères.
Processus complet d'optimisation Dockerfile
5 astuces pour réduire la taille de l'image de 80 %, de 3,2 Go à 180 Mo (-94 %)
⏱️ Estimated time: 1 hr
- 1
Step 1: Astuce 1 : utiliser une image de base Alpine
Avantages d'Alpine :
• Volume réduit (5 Mo seulement, Ubuntu ~200 Mo)
• Sécurité renforcée (surface d'attaque minimale)
• Gestionnaire de paquets simple (apk)
• Adapté à la production
Utilisation :
• Remplacer FROM ubuntu:20.04 par FROM alpine:latest
• Utiliser apk au lieu de apt-get : apk add --no-cache nodejs npm
• Image de 200 Mo à 5 Mo (-95 %) - 2
Step 2: Astuces 2-3 : fusionner les RUN et build multi-étapes
Fusion des RUN :
• Regrouper plusieurs RUN en un seul pour réduire les couches
• Enchaîner avec &&, utiliser \ pour la lisibilité
• Nettoyer le cache en fin de commande :
RUN apt-get update && \
apt-get install -y nodejs npm && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Build multi-étapes :
• Première étape : image complète pour compiler
• Deuxième étape : image minimale pour l'exécution
• Ne garder que les fichiers runtime, pas les outils de build
• Réduction drastique du volume - 3
Step 3: Astuces 4-5 : .dockerignore et nettoyage du cache
Configurer .dockerignore :
• Exclure node_modules, .git, .env, dist, etc.
• Réduire le contexte de build, accélérer la construction
Nettoyage du cache :
• Option --no-cache : apk add --no-cache
• Cache apt : apt-get clean && rm -rf /var/lib/apt/lists/*
• Cache npm : npm cache clean --force
• Réduction du volume de l'image
FAQ
Quelles sont les 5 astuces d'optimisation Dockerfile ?
1) Image de base Alpine (Ubuntu 200 Mo → Alpine 5 Mo, -95 %)
2) Fusionner les RUN (moins de couches, image plus légère)
3) Build multi-étapes (ne garder que le runtime, pas les outils de compilation)
4) .dockerignore (exclure node_modules, .git, etc.)
5) Nettoyage du cache (apt-get clean, npm cache clean, etc.)
Résultats :
• Image Node.js de 3,2 Go à 180 Mo (-94 %)
• De 1,2 Go à 180 Mo (-85 %)
• Déploiement de 30 min à quelques minutes
• Réduction significative du volume
Pourquoi Alpine est-elle une meilleure image de base ?
• Volume réduit (5 Mo vs Ubuntu ~200 Mo)
• Sécurité (surface d'attaque minimale)
• Gestionnaire simple (apk)
• Adaptée à la production
Utilisation :
• Remplacer FROM ubuntu:20.04 par FROM alpine:latest
• apk au lieu de apt-get : apk add --no-cache nodejs npm
• Image de 200 Mo à 5 Mo (-95 %)
Comment fusionner les instructions RUN ?
• Regrouper plusieurs RUN en un seul pour réduire les couches
• Enchaîner avec &&
• Utiliser \ pour la lisibilité
• Nettoyer le cache en fin de commande :
RUN apt-get update && \
apt-get install -y nodejs npm && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Moins de couches, image plus légère, build plus efficace.
Quelles sont les bonnes pratiques d'optimisation Dockerfile ?
• Image de base Alpine
• Fusionner les RUN
• Build multi-étapes
• Configurer .dockerignore
• Nettoyer le cache
• Mettre à jour régulièrement l'image de base
Ces astuces se combinent : Alpine + multi-étapes règlent ~80 % du volume, le reste vient du nettoyage et de l'exclusion.
L'optimisation Dockerfile est un processus continu — vérifiez la taille à chaque build, l'habitude fait le reste.
10 min de lecture · Publié le: 17 déc. 2025 · Mis à jour le: 27 juil. 2026
Guide pratique Docker
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Tutoriel Dockerfile : construire votre première image Docker (avec exemples)
Apprenez à écrire un Dockerfile pas à pas : FROM, RUN, COPY et les instructions clés, les pièges des débutants et un cas Node.js pratique — prêt à dockeriser votre projet.
Partie 3 sur 38
Suivant
Build multi-étapes Docker : réduire les images Go/Java/Rust de GB à MB
Analyse approfondie du build multi-étapes Docker, avec cas réels montrant comment optimiser une image Go de 98 %, Java de 86 % et Rust de 99,4 %. Dockerfile complets et retours d'expérience.
Partie 5 sur 38



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire