Réduire la taille des images Docker : un guide pratique pour des builds plus rapides
Les images Docker constituent l'épine dorsale des déploiements cloud modernes, mais des images structurées de manière inefficace peuvent entraîner des frictions importantes. Les images trop volumineuses gaspillent l'espace de stockage, ralentissent les pipelines CI/CD, augmentent les temps de déploiement (surtout dans les environnements sans serveur ou les emplacements distants) et peuvent potentiellement élargir la surface d'attaque en matière de sécurité.
L'optimisation de la taille des images est une étape cruciale dans l'optimisation des performances des conteneurs. Ce guide fournit des techniques concrètes et expertes—en se concentrant principalement sur les builds multi-étapes, la sélection d'images de base minimales et des pratiques Dockerfile rigoureuses—pour vous aider à obtenir des applications conteneurisées considérablement plus légères, plus rapides et plus sécurisées.
1. La fondation : choisir la bonne image de base
La manière la plus immédiate d'influer sur la taille d'une image est de sélectionner une fondation minimale. De nombreuses images par défaut contiennent des utilitaires nécessaires, des compilateurs et de la documentation qui sont totalement inutiles pour l'environnement d'exécution.
Utiliser des images Alpine ou Distroless
Alpine Linux est le choix minimal standard. Il est basé sur Musl libc (au lieu de Glibc utilisé par Debian/Ubuntu) et aboutit généralement à des images de base mesurées en mégabytes (Mo) à un seul chiffre.
| Type d'image | Plage de taille | Cas d'utilisation |
|---|---|---|
full/latest (ex. node:18) |
500 Mo + | Développement, tests, débogage |
slim (ex. node:18-slim) |
150 - 250 Mo | Production (lorsque Glibc est requis) |
alpine (ex. node:18-alpine) |
50 - 100 Mo | Production (meilleure réduction de taille) |
| Distroless | < 10 Mo | Environnement de production hautement sécurisé et en exécution seule |
Conseil : Si votre application dépend fortement de fonctionnalités spécifiques de Glibc, Alpine pourrait introduire des incompatibilités d'exécution. Testez toujours rigoureusement lors de la migration vers une base Alpine.
Utiliser les tags minimaux officiels spécifiques au fournisseur
Si vous devez utiliser un environnement de programmation spécifique, privilégiez toujours les tags minimaux officiellement maintenus par le fournisseur (ex. python:3.10-slim, openjdk:17-jdk-alpine). Ceux-ci sont conçus pour supprimer les composants non essentiels tout en maintenant la compatibilité.
2. La technique puissante : les builds multi-étapes
Les builds multi-étapes (Multi-Stage Builds) sont la technique la plus efficace pour réduire la taille des images, en particulier pour les applications compilées ou lourdes en dépendances (comme Java, Go, React/Node ou C++).
Cette technique sépare l'environnement de build (qui nécessite des compilateurs, des outils de test et de grands packages de dépendances) de l'environnement d'exécution final.
Comment fonctionnent les builds multi-étapes
- Étape 1 (Builder) : Utilise une image volumineuse et riche en fonctionnalités (ex.
golang:latest,node:lts) pour compiler ou packager l'application. - Étape 2 (Runner) : Utilise une image d'exécution minimale (ex.
alpine,scratchoudistroless). - L'étape finale copie sélectivement uniquement les artefacts nécessaires (ex. binaires compilés, actifs minifiés) depuis l'étape de construction, en ignorant tous les outils de build et les caches.
Exemple de build multi-étapes (Go)
Dans cet exemple, l'étape de construction est ignorée, ce qui donne une image finale extrêmement petite basée sur scratch (l'image de base vide).
# Stage 1: The Build Environment
FROM golang:1.21 AS builder
WORKDIR /app
# Copy source code and download dependencies
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Build the static binary
RUN CGO_ENABLED=0 GOOS=linux go build -a -o /app/server .
# Stage 2: The Final Runtime Environment
# 'scratch' is the smallest possible base image
FROM scratch
# Set execution path (optional, but good practice)
WORKDIR /usr/bin/
# Copy only the compiled binary from the builder stage
COPY --from=builder /app/server .
# Define the command to run the application
ENTRYPOINT ["/usr/bin/server"]
En mettant en œuvre ce modèle, une image qui aurait pu faire 800 Mo (si elle était construite sur golang:1.21) peut souvent être réduite à 5-10 Mo.
3. Techniques d'optimisation du Dockerfile
Même avec des images de base minimales et des builds multi-étapes, un Dockerfile non optimisé peut toujours entraîner un encombrement inutile en raison d'une gestion inefficace des couches.
Minimiser les couches en combinant les commandes RUN
Chaque instruction RUN crée une nouvelle couche immuable. Si vous installez des dépendances puis les supprimez lors d'étapes séparées, l'étape de suppression n'ajoute qu'une nouvelle couche, mais les fichiers de la couche précédente restent stockés dans l'historique de l'image (et contribuent à sa taille).
Combinez toujours l'installation des dépendances et le nettoyage dans une seule instruction RUN, en utilisant l'opérateur && et la continuation de ligne (\).
Inefficace (Crée deux grandes couches) :
RUN apt-get update
RUN apt-get install -y build-essential
RUN apt-get remove -y build-essential && rm -rf /var/lib/apt/lists/*
Optimisé (Crée une seule couche plus petite) :
RUN apt-get update && \n apt-get install -y --no-install-recommends build-essential \n && apt-get clean && rm -rf /var/lib/apt/lists/*
Bonne pratique : Lors de l'utilisation de
apt-get install, incluez toujours l'indicateur--no-install-recommendspour ignorer l'installation des packages non essentiels, et assurez-vous de nettoyer les listes de packages et les fichiers temporaires (/var/cache/apt/archives/ou/var/lib/apt/lists/*) dans la même commandeRUN.
Utiliser efficacement .dockerignore
Le fichier .dockerignore empêche Docker de copier des fichiers non pertinents (qui pourraient inclure de gros fichiers temporaires, des répertoires .git, des journaux de développement ou des dossiers node_modules volumineux) dans le contexte de build. Même si ces fichiers ne sont pas copiés dans l'image finale, ils ralentissent néanmoins le processus de build et peuvent encombrer les couches de build intermédiaires.
Exemple de .dockerignore :
# Ignorer les fichiers de développement et les caches
.git
.gitignore
.env
# Ignorer les artefacts de build de la machine hôte
node_modules
target/
dist/
# Ignorer les fichiers d'éditeur
*.log
*.bak
Préférer COPY à ADD
Bien que ADD offre des fonctionnalités comme l'extraction automatique des archives tar locales et la récupération d'URL distantes, COPY est généralement préféré pour le simple transfert de fichiers. Si ADD extrait une archive, les données décompressées contribuent à une taille de couche plus importante. Tenez-vous-en à COPY sauf si vous avez explicitement besoin de la fonction d'extraction d'archive.
4. Analyse et révision
Une fois que vous avez mis en œuvre ces techniques, il est essentiel d'analyser les résultats pour garantir une efficacité maximale.
Inspection des couches d'image
Utilisez la commande docker history pour voir exactement la contribution de chaque étape à la taille finale de l'image. Cela aide à identifier les étapes qui ajoutent par inadvertance du volume.
docker history my-optimized-app
# Exemple de sortie :
# IMAGE CREATED SIZE COMMENT
# <a> 3 minutes ago 4.8MB COPY --from=builder ...
# <b> 3 weeks ago 4.2MB /bin/sh -c #(nop) WORKDIR /usr/bin/
# <c> 3 weeks ago 3.4MB /bin/sh -c #(nop) CMD [...]
Utiliser des outils externes
Des outils comme Dive (https://github.com/wagoodman/dive) fournissent une interface visuelle pour explorer le contenu de chaque couche, identifiant les fichiers redondants ou les caches cachés qui augmentent la taille de l'image.
Résumé des meilleures pratiques
| Technique | Description | Impact |
|---|---|---|
| Builds multi-étapes | Séparer les dépendances de build (Étape 1) des artefacts d'exécution (Étape 2). | Réduction énorme, généralement 80 %+ |
| Images de base minimales | Utiliser alpine, slim ou distroless. |
Réduction significative de la taille de base |
| Combinaison de couches | Utiliser && et \ pour enchaîner les commandes RUN et les étapes de nettoyage. |
Optimise la mise en cache des couches et réduit le nombre total de couches |
Utilisation de .dockerignore |
Exclure les fichiers sources, les caches et les journaux inutiles du contexte de build. | Builds plus rapides, couches intermédiaires plus petites |
| Nettoyage des dépendances | Supprimer les dépendances de build et les caches de packages immédiatement après l'installation. | Élimine les fichiers résiduels qui gonflent la taille de l'image |
En appliquant systématiquement les builds multi-étapes et une gestion méticuleuse du Dockerfile, vous pouvez obtenir des images Docker considérablement plus petites, plus rapides et plus efficaces, ce qui entraîne des temps de déploiement améliorés et des coûts opérationnels réduits.