Aller au contenu
Conteneurs & Orchestration medium

Optimiser la taille des images de container

17 min de lecture

Votre image Docker fait 1 Go alors qu’elle pourrait en faire 50 Mo ? Ce guide vous accompagne pas à pas pour réduire la taille de vos images de 50 à 90%. Nous commencerons par comprendre pourquoi c’est crucial, puis nous appliquerons les techniques dans l’ordre d’impact : du plus efficace au plus fin.

  • Pourquoi optimiser : impact sur les coûts, la sécurité et les performances de déploiement
  • Choisir son image de base : scratch, distroless, Alpine, Chainguard — quel choix pour quel usage ?
  • Multi-stage build : séparer build et runtime pour exclure les outils de compilation
  • Optimisations fines : .dockerignore, nettoyage des caches, réduction des couches

Avant de plonger dans les techniques, comprenons pourquoi la taille des images est un enjeu critique. Une image de 1 Go au lieu de 50 Mo a des conséquences bien réelles.

MétriqueImage 1 GoImage 50 MoÉconomie
Stockage registry (100 images)100 Go5 Go95%
Bande passante déploiement1 Go/nœud50 Mo/nœud95%
Temps de pull (100 Mbps)~80 sec~4 sec95%

Sur un cluster Kubernetes de 50 nœuds avec des déploiements fréquents, ces chiffres se traduisent en milliers d’euros de coûts cloud évités.

Une image volumineuse contient généralement :

  • Des shells (bash, sh) permettant l’exécution de commandes malveillantes
  • Des gestionnaires de paquets (apt, yum) pour installer des backdoors
  • Des outils réseau (curl, wget) pour exfiltrer des données

En production, vous n’avez besoin de rien de tout cela.

PhaseImage volumineuseImage optimisée
Pull initialLent⚡ Rapide
Scaling (nouveaux pods)Attente du pullQuasi-instantané
RollbackLent si ancienne image évincéeRapide
CI/CD pipelineBuild + push longsCycles courts

Avant d’optimiser, mesurez ! Utilisez ces commandes pour diagnostiquer :

Fenêtre de terminal
# Taille des images locales
docker images --format "table {{.Repository}}:{{.Tag}} {{.Size}}"
# Détail des couches d'une image
docker history <image>:<tag>
# Analyse interactive avec Dive
dive <image>:<tag>

Maintenant que vous comprenez les enjeux, passons aux techniques d’optimisation, dans l’ordre d’impact.

Le choix de l’image de base impacte directement la taille et la sécurité de vos conteneurs. Une image standard Debian peut contenir des centaines de CVE, alors qu’une image Chainguard en a généralement zéro.

ImageTailleCVE typiquesShellLibcCas d’usage
scratch0 Mo0Binaires statiques Go/Rust
cgr.dev/chainguard/static~2 Mo0Binaires statiques + certificats
gcr.io/distroless/static~2 Mo0-5Binaires statiques Google
alpine:3.20~7 Mo0-10muslUsage général léger
cgr.dev/chainguard/wolfi-base~12 Mo0glibcApps nécessitant glibc
gcr.io/distroless/base~20 Mo0-10glibcApps avec libc dynamique
debian:bookworm-slim~75 Mo50-150glibcCompatibilité maximale
ubuntu:24.04~78 Mo50-200glibcEnvironnement familier

scratch est une image totalement vide (0 octet). Elle convient uniquement aux binaires compilés statiquement qui n’ont pas besoin de libc :

# Pour Go avec compilation statique
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/myapp
FROM scratch
COPY --from=builder /app/myapp /myapp
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/myapp"]

Attention : sans certificats SSL, vos appels HTTPS échoueront. Copiez-les depuis le builder ou utilisez distroless/static qui les inclut.

Les images distroless (Google) et Chainguard ne contiennent ni shell, ni gestionnaire de paquets. Un attaquant qui compromet votre conteneur ne peut pas exécuter bash, wget ou installer d’outils.

# Application Java avec distroless
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
COPY . .
RUN ./mvnw package -DskipTests
FROM gcr.io/distroless/java21-debian12
COPY --from=builder /app/target/*.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

Pour déboguer en développement, utilisez le tag :debug qui ajoute un shell BusyBox : gcr.io/distroless/java21-debian12:debug.

Alpine utilise musl libc au lieu de glibc, ce qui peut causer des problèmes de compatibilité :

Exemple d’image Alpine optimisée :

FROM alpine:3.20
RUN apk --no-cache add curl ca-certificates

Wolfi est une distribution créée par Chainguard spécifiquement pour les conteneurs. Elle combine :

  • glibc pour la compatibilité maximale
  • Mises à jour de sécurité quotidiennes
  • 0 CVE garanti à la publication
  • Construction avec apko (déclaratif, reproductible)
# Application Python avec Chainguard
FROM cgr.dev/chainguard/python:latest-dev AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt --target=/app/deps
FROM cgr.dev/chainguard/python:latest
WORKDIR /app
COPY --from=builder /app/deps /app/deps
COPY . .
ENV PYTHONPATH=/app/deps
ENTRYPOINT ["python", "app.py"]
LangageImage recommandéePourquoi
Goscratch ou distroless/staticBinaire statique, 0 dépendance
Rustscratch ou distroless/staticCompilation statique avec musl
Javadistroless/java21 ou chainguard/jreJRE minimal, pas de shell
Pythonchainguard/python ou python:3.12-slimglibc pour les extensions natives
Node.jschainguard/node ou node:20-slimÉvite les problèmes musl
.NETmcr.microsoft.com/dotnet/runtimeImages Microsoft optimisées

Le multi-stage est une fonctionnalité introduite avec Docker 17.05 qui permet de décrire plusieurs étapes dans un même Dockerfile. Chaque stage commence par une instruction FROM.

Principe du multi-stage build

Principe en 3 stages :

  1. Stage base : environnement commun (OS + dépendances runtime)
  2. Stage builder : compilation, téléchargement des dépendances, build
  3. Stage production : copie sélective uniquement du résultat final

Syntaxe clé :

  • Nommez vos stages avec AS <nom> : FROM alpine:3.18 AS builder
  • Copiez entre stages avec COPY --from=<nom> : COPY --from=builder /app/dist /app
FROM alpine:3.10.2
RUN apk update && apk upgrade
RUN apk add --no-cache python3 openssl
RUN apk add --no-cache --virtual .build-deps python3-dev gcc ca-certificates libffi-dev openssl-dev build-base
RUN pip3 install --no-cache-dir --upgrade pip ansible
RUN apk del .build-deps

Si vous ne maitrisez pas le gestionnaire de paquets apk, je vous recommande de lire mon guide sur apk pour mieux comprendre son fonctionnement.

On contrôle la taille de ‘image

Fenêtre de terminal
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ansible 0.1 5e43ab54b9a0 56 seconds ago 393MB
FROM alpine:3.10.2 as base
RUN apk update && apk --no-cache upgrade && apk add --no-cache python3 openssl
FROM base as builder
RUN apk add --no-cache --virtual .build-deps python3-dev gcc ca-certificates libffi-dev openssl-dev build-base
RUN python3 -m venv /opt/venv
Make sure we use the virtualenv:
ENV PATH="/opt/venv/bin:$PATH"
RUN pip3 install --no-cache-dir ansible
FROM base
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

On contrôle :

Fenêtre de terminal
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ansible 0.2 304de2e8b235 51 seconds ago 165MB

On voit que l’image ne fait plus que 165MB contre 393MB pour la version non optimisée.

Cette approche est applicable à d’autres langages et plateformes, permettant d’optimiser les images de conteneurs pour la production, peu importe que vous utilisiez Docker, Podman ou un autre moteur de conteneurisation, voir un orchestrateur tel que Kubernetes.

Un fichier .dockerignore joue un rôle important dans l’optimisation de vos images de conteneurs en excluant les fichiers et répertoires inutiles lors de la construction de l’image. Tout comme un fichier .gitignore, il empêche l’inclusion de fichiers temporaires, de configurations locales, ou de dépendances non essentielles dans l’image finale, ce qui permet de réduire sa taille de manière significative.

Par exemple, si votre projet contient des fichiers comme .env, node_modules, ou des dossiers de test, vous pouvez les exclure facilement avec un .dockerignore bien configuré :

Exemple :

# ignore all markdown files (md) beside all README*.md other than README-secret.md
*.md
!README*.md
README-secret.md

Cela garantit que ces fichiers ne seront pas copiés dans le contexte de construction, réduisant ainsi la taille de l’image et évitant les risques liés à la fuite d’informations sensibles. En ne copiant que les fichiers nécessaires, votre image devient non seulement plus légère, mais aussi plus sécurisée.

Une fois votre image de base choisie et votre multi-stage configuré, affinez les installations de paquets pour grappiller les derniers Mo.

Chaque paquet installé ajoute du poids et des vulnérabilités potentielles. N’installez que le strict nécessaire pour l’exécution :

Par défaut, apt-get installe les paquets “recommandés” qui ne sont pas toujours nécessaires. Bloquez ce comportement :

RUN apt-get update && apt-get install -y --no-install-recommends nginx \
&& rm -rf /var/lib/apt/lists/*

Important : supprimez toujours /var/lib/apt/lists/* dans le même RUN pour ne pas conserver les 30-50 Mo de listes de paquets.

# --no-cache évite de stocker l'index des paquets
RUN apk add --no-cache curl ca-certificates
# --virtual pour les dépendances de build temporaires
RUN apk add --no-cache --virtual .build-deps gcc make musl-dev \
&& make \
&& apk del .build-deps

L’option --virtual crée un méta-paquet que vous pouvez supprimer en une commande, emportant toutes ses dépendances.

Chaque instruction RUN, COPY, ou ADD dans un fichier de construction comme un Dockerfile crée une nouvelle couche dans l’image de conteneur. Plus vous avez de couches, plus l’image devient volumineuse et complexe. Pour réduire la taille de l’image, il est donc essentiel de combiner les commandes là où c’est possible.

Par exemple, au lieu de séparer les commandes RUN en plusieurs lignes, vous pouvez les combiner en une seule instruction :

RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

Dans cet exemple, les commandes sont combinées en une seule instruction RUN, ce qui crée une seule couche au lieu de plusieurs. Cette pratique permet non seulement de réduire la taille finale de l’image, mais aussi de simplifier sa gestion et son analyse.

Une autre bonne pratique consiste à supprimer les fichiers temporaires ou les caches à la fin de la commande combinée pour s’assurer qu’ils ne sont pas inclus dans l’image finale. Ainsi, en regroupant les commandes et en nettoyant après chaque installation, vous obtenez une image de conteneur plus légère, ce qui améliore la vitesse de déploiement et réduit l’utilisation des ressources, peu importe le moteur de conteneur utilisé (Docker, Podman, etc.).

Cette technique est essentielle pour maintenir des images efficaces et éviter l’accumulation de données inutiles au fil des modifications apportées à vos conteneurs.

Analyser vos images de conteneurs est une étape importante pour identifier les opportunités d’optimisation. Des outils comme Dive vous permettent de visualiser chaque couche de votre image et de comprendre exactement ce qui contribue à sa taille. Dive vous aide à repérer les fichiers ou les couches redondantes, à voir où l’espace est gaspillé et à prendre des décisions éclairées pour réduire l’image.

Par exemple, après avoir construit une image, vous pouvez exécuter Dive pour analyser les couches :

Fenêtre de terminal
dive <nom_de_l_image>

Dive affichera une vue interactive où chaque couche est détaillée, avec la possibilité de voir quels fichiers ont été ajoutés ou modifiés à chaque étape. Cela vous permet d’identifier les couches inutiles ou surdimensionnées, qui peuvent être consolidées ou retirées dans une prochaine version de votre Dockerfile.

dive

En appliquant ces analyses, vous pouvez affiner votre processus de construction d’images pour obtenir des conteneurs plus légers et performants. Ces optimisations contribuent directement à des déploiements plus rapides et à une utilisation plus efficace des ressources dans vos environnements de production.

  1. Multi-stage build : LA technique la plus efficace, réduction de 50-80%
  2. —no-install-recommends : évite les dépendances inutiles sur Debian/Ubuntu
  3. —no-cache sur Alpine : ne conserve pas le cache apk
  4. Combinez les RUN : moins de couches = image plus légère
  5. .dockerignore : exclut node_modules, .git, fichiers de test
  6. Analysez avec Dive : visualisez chaque couche pour optimiser
PrioritéImageTailleSécurité
🥇 Sécurité maxChainguard/Wolfi~2-12 Mo0 CVE
🥈 Équilibredistroless~2-20 Mo0-10 CVE
🥉 CompatibilitéAlpine~7 Mo0-10 CVE (musl)
4️⃣ DébutantDebian slim~75 Mo50-150 CVE

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

15 questions
8 min.
80% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre