Aller au contenu principal

Docker Dive — Radiographiez vos images et éliminez le gaspillage

Utilisez dive pour inspecter chaque layer d'une image Docker, repérer les mégaoctets gaspillés par des fichiers supprimés, et passer étape par étape d'une image obèse de 1 Go à un build compact et efficace.

· 17 minutes de lecture
Voir la source
Christophe
Amoureux de Markdown, WSL et Docker ~ Curieux insatiable.
Cet article existe aussi en anglais — lire la version originale.

Traduction automatique

Cette traduction a été produite par une IA. Elle peut contenir des contresens ou des maladresses, en particulier sur les termes techniques. En cas de doute, consultez l'article de référence en anglais — c'est lui qui fait foi.

Docker Dive — Radiographiez vos images et éliminez le gaspillage

⚡
TL;DR

dive est un outil open source en ligne de commande qui affiche chaque layer d'une image Docker sous forme d'arborescence de fichiers interactive, en mettant en évidence exactement quels fichiers sont gaspillés — présents dans un layer mais supprimés dans un layer suivant, et pourtant toujours embarqués dans l'image finale. Cet article part d'un Dockerfile volontairement catastrophique, montre comment dive expose le gaspillage, puis le corrige en trois étapes successives : nettoyage des caches apt dans le même layer, fusion de plusieurs instructions RUN en une seule, et passage à un multi-stage build qui réduit l'image de 80 %. En bonus, on pousse l'idée à son extrême avec FROM scratch — une image contenant un seul binaire et rien d'autre.

Mince. Je viens de lancer docker images et voilà ce que j'ai vu : myapp:latest — 1.19 GB. Une application web Python avec cinq dépendances, et elle a gonflé au-delà du gigaoctet. Quelque chose cloche sérieusement dans ce Dockerfile.

Le problème avec l'obésité des images Docker, c'est qu'elle est invisible tant qu'on ne la cherche pas. Vos containers démarrent, votre app tourne, tout semble normal — et pendant ce temps vous expédiez 400 Mo de cache apt et d'outils de build en production à chaque déploiement.

C'est là qu'intervient dive. C'est un inspecteur d'images en mode terminal qui permet de naviguer dans les layers de n'importe quelle image Docker, de voir exactement ce que chaque instruction RUN, COPY et ADD a ajouté ou supprimé, et d'obtenir un score d'efficacité sans pitié en bas de l'écran. Voyez ça comme une machine à rayons X pour vos images.

Qu'est-ce que Dive ?​

Au fond, une image Docker est une pile de layers en lecture seule. Chaque instruction de votre Dockerfile qui touche au système de fichiers crée un nouveau layer. apt-get install — nouveau layer. pip install — nouveau layer. COPY — nouveau layer.

Voilà le piège : si vous installez un package dans le layer 3, puis supprimez son cache dans le layer 5, les données du cache sont toujours dans l'image. La suppression marque seulement les fichiers comme « opaques » dans le layer supérieur — les octets du layer 3 sont toujours là, téléchargés à chaque docker pull.

dive rend tout cela visible. Panneau de gauche : l'arborescence de fichiers du layer sélectionné. Panneau de droite : la liste des layers avec leurs tailles. En bas : votre score d'efficacité et l'espace total gaspillé.

Raccourcis clavier dans dive

Appuyez sur Tab pour basculer entre la liste des layers et l'arborescence de fichiers. Les flèches permettent de naviguer entre les layers. Space replie/déplie les répertoires de l'arborescence. Les fichiers affichés en jaune sont modifiés, ceux en rouge sont supprimés — ces derniers sont vos octets gaspillés.

Lancer Dive — aucune installation nécessaire​

Vous me connaissez bien maintenant : j'aime mettre les choses dans des containers. Et dive dispose d'une image Docker officielle, donc aucune raison d'installer quoi que ce soit sur votre host :

>user@machine: ~/myapp

$ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest myapp:latest

Le montage -v /var/run/docker.sock:/var/run/docker.sock est obligatoire : c'est par là que le container dive accède au daemon Docker de votre host pour inspecter les images. Si cela vous met mal à l'aise — et c'est une inquiétude légitime — consultez l'article sur Docker-out-of-Docker pour le contexte de sécurité autour du montage de la socket.

Si vous préférez un binaire local, dive est disponible sous Linux, macOS et Windows :

>user@machine: ~

$ wget -q https://github.com/wagoodman/dive/releases/latest/download/dive_linux_amd64.tar.gz -O - | tar xz dive && sudo mv dive /usr/local/bin/

Dans les deux cas, l'usage est identique : dive <image-name>.

Le patient : un Dockerfile volontairement catastrophique​

Construisons quelque chose de vraiment mauvais pour avoir de quoi analyser. Notre app est une petite API Flask — rien d'extraordinaire, juste assez pour tirer de vraies dépendances :

from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def index():
return "Hello from Docker!"
@app.route("/health")
def health():
return jsonify({"status": "ok"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)

Ce script crée une application web Flask avec deux endpoints simples : un endpoint racine qui retourne un message texte, et un endpoint /health qui retourne un statut JSON. Quand on exécute le script directement, l'application démarre sur le port 5000 accessible depuis n'importe quelle interface réseau.

Et le Dockerfile — écrit sans le moindre égard pour la taille de l'image :

# syntax=docker/dockerfile:1
FROM ubuntu:24.04
RUN apt-get update
RUN apt-get install -y \
curl \
wget \
git \
python3 \
python3-pip \
build-essential \
python3-dev \
libssl-dev \
pkg-config
RUN pip3 install flask requests numpy pandas scikit-learn --break-system-packages
RUN mkdir /app
COPY app.py /app/
WORKDIR /app
CMD ["python3", "app.py"]

Ce Dockerfile construit une image Docker basée sur Ubuntu 24.04 en installant les outils de développement essentiels (curl, wget, git), Python avec ses dépendances de compilation, puis les bibliothèques Python courantes (flask, requests, numpy, pandas, scikit-learn). Il copie ensuite le fichier app.py dans le conteneur et le lance au démarrage.

Construisons-le et voyons à quoi on a affaire :

>user@machine: ~/myapp

$ docker build -t myapp:bad -f Dockerfile.bad .

[+] Building 142.3s (9/9) FINISHED

$ docker images myapp:bad

REPOSITORY TAG IMAGE ID SIZE myapp bad 3f8c1a9e2b71 1.19GB

1,19 Go. Pour une app Flask de cinq fichiers. Voyons exactement pourquoi.

Le diagnostic : ce que dive révèle​

>user@machine: ~/myapp

$ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest myapp:bad

Dans l'interface interactive, le panneau de droite (la liste des layers) ressemble à ceci :

Cmp Size Command
212 MB FROM ubuntu:24.04
38 MB RUN apt-get update
368 MB RUN apt-get install -y curl wget git python3...
184 MB RUN pip3 install flask requests numpy pandas...
0.0 B RUN mkdir /app
1.2 KB COPY app.py /app/

Le panneau de gauche affiche l'arborescence du layer sélectionné. Quand vous sélectionnez le layer apt-get update, vous verrez /var/lib/apt/lists/ rempli de centaines de mégaoctets de fichiers d'index de packages — et ils sont toujours là dans chaque layer suivant.

Pour un résultat non interactif et scriptable, utilisez le mode CI :

>user@machine: ~/myapp
$ CI=true docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest myapp:bad

  Using default CI config
Image Source: docker://myapp:bad
Fetching image... (this can take a while for large images)
Analyzing image...
  efficiency: 0.6341 (63.41%)
  wastedBytes: 438071296 bytes (438 MB)
  userWastedPercent: 36.59%
Result:FAIL [Total:3] [Passed:0] [Failed:3] [Warn:0] [Skipped:0]

61,89 % d'efficacité. 438 Mo gaspillés. C'est brutal, et c'est entièrement de notre faute.

Que signifie exactement « octets gaspillés » ?

Dive compte un fichier comme « gaspillé » lorsqu'il existe dans un layer inférieur mais est supprimé ou écrasé dans un layer supérieur. Les octets d'origine du layer inférieur sont toujours physiquement présents dans l'image — ils ne sont simplement plus visibles dans le container en cours d'exécution. Chaque docker pull les télécharge quand même.

Correctif n°1 — nettoyer le bazar apt dans le même layer​

La première erreur, et la plus fréquente : séparer apt-get update du nettoyage. La règle est simple — tout ce que vous voulez faire disparaître doit être supprimé dans la même instruction RUN.

# syntax=docker/dockerfile:1
FROM ubuntu:24.04
RUN apt-get update && \
apt-get install -y \
curl \
wget \
git \
python3 \
python3-pip \
build-essential \
python3-dev \
libssl-dev \
pkg-config && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN pip3 install flask requests numpy pandas scikit-learn --break-system-packages
RUN mkdir /app
COPY app.py /app/
WORKDIR /app
CMD ["python3", "app.py"]

Ce Dockerfile construit une image Docker basée sur Ubuntu 24.04 qui installe les dépendances système et Python nécessaires, puis configure un répertoire applicatif pour exécuter un script Python. L'image nettoie les fichiers temporaires après l'installation pour réduire sa taille, installe plusieurs bibliothèques Python populaires, et définit le point d'entrée pour lancer app.py au démarrage du conteneur.

Le changement clé est l'ajout de apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* à la fin du bloc RUN d'installation apt. Cela s'exécute dans le même layer, donc les fichiers de cache ne sont jamais committés dans l'image.

>user@machine: ~/myapp

$ docker build -t myapp:v2 -f Dockerfile.v2 .

$ docker images myapp:v2

REPOSITORY TAG IMAGE ID SIZE myapp v2 7a4e0f1c9d83 712MB

712 Mo au lieu de 1,19 Go. Près de 500 Mo envolés. Confirmons l'amélioration avec dive :

>user@machine: ~/myapp
$ CI=true docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest myapp:v2

  Using default CI config
Image Source: docker://myapp:v2
Fetching image... (this can take a while for large images)
Analyzing image...
  efficiency: 0.9823 (98.23%)
  wastedBytes: 8192 bytes (8.2 kB)
  userWastedPercent: 1.77%
Result:PASS [Total:3] [Passed:3] [Failed:0] [Warn:0] [Skipped:0]

98,23 % d'efficacité — un bond énorme. Mais on est encore à 712 Mo pour une minuscule app Flask. Il reste du travail.

Correctif n°2 — moins de layers, moins de surcharge​

Regardez à nouveau la liste des layers de Dockerfile.bad. Chaque instruction RUN séparée crée son propre layer. Même si un layer n'ajoute « qu'un » répertoire ou quelques kilooctets, chaque layer a un coût en métadonnées — et surtout, séparer l'installation du nettoyage rend impossible de les garder dans le même layer.

La solution : regrouper les opérations liées dans une seule chaîne RUN avec && :

# syntax=docker/dockerfile:1
FROM ubuntu:24.04
RUN apt-get update && \
apt-get install -y \
curl \
wget \
git \
python3 \
python3-pip \
build-essential \
python3-dev \
libssl-dev \
pkg-config && \
pip3 install flask requests numpy pandas scikit-learn --break-system-packages && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
find /usr -name "*.pyc" -delete 2>/dev/null; \
find /usr -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null; true
RUN mkdir /app
COPY app.py /app/
WORKDIR /app
CMD ["python3", "app.py"]

Ce Dockerfile construit une image Docker basée sur Ubuntu 24.04, installe les dépendances système et Python nécessaires pour exécuter une application Flask, nettoie les artefacts inutiles pour réduire la taille de l'image, puis configure le répertoire de travail et lance l'application Python.

Au-delà du nombre réduit de layers, remarquez les commandes find à la fin : elles suppriment tous les fichiers compilés .pyc et les répertoires __pycache__ que pip crée pendant l'installation. Du gaspillage pur dans une image de production.

>user@machine: ~/myapp

$ docker build -t myapp:v3 -f Dockerfile.v3 .

$ docker images myapp

REPOSITORY TAG IMAGE ID SIZE myapp bad 3f8c1a9e2b71 1.19GB myapp v2 7a4e0f1c9d83 712MB myapp v3 2b9d4e7f1c05 689MB

L'écart entre v2 et v3 est modeste ici — environ 23 Mo. Le vrai intérêt du regroupement des instructions RUN, c'est la clarté et la justesse : vous ne pouvez plus mettre accidentellement le nettoyage dans un autre layer.

Mais 689 Mo, c'est encore énorme pour une app Flask

Nous avons toujours build-essential, python3-dev, libssl-dev, pkg-config, wget et git dans l'image de production. Ce sont des outils de build — ils étaient nécessaires pour compiler certains packages pip, mais ils n'ont rien à faire dans l'image qui tourne en production. C'est ici que l'approche mono-stage atteint sa limite.

Correctif n°3 — le multi-stage build​

L'idée centrale du multi-stage build est simple : un FROM pour construire, un autre FROM pour exécuter. Le stage builder peut être aussi gros qu'il veut — outils, caches, compilateurs — puisque seul le stage final est expédié.

# syntax=docker/dockerfile:1
FROM python:3.12 AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt --target ./packages
FROM python:3.12-slim AS production
WORKDIR /app
COPY --from=builder /build/packages ./packages
ENV PYTHONPATH=/app/packages
COPY app.py .
CMD ["python3", "app.py"]

Ce Dockerfile utilise une construction multi-étapes pour réduire la taille de l'image finale. D'abord, une étape builder installe toutes les dépendances Python dans un répertoire isolé, puis une étape production plus légère (basée sur python:3.12-slim) copie uniquement les packages compilés et le code applicatif, sans conserver les outils de compilation inutiles.

Ce qui se passe ici :

  1. FROM python:3.12 AS builder — image Python complète, utilisée uniquement pour installer les packages
  2. pip install --target ./packages — installe tout dans un répertoire local (pas à l'échelle du système)
  3. FROM python:3.12-slim AS production — l'image de base slim, environ 130 Mo
  4. COPY --from=builder /build/packages ./packages — seuls les packages installés franchissent la frontière ; pas d'outils de build, pas de cache pip, pas de listes apt
>user@machine: ~/myapp

$ docker build -t myapp:multistage -f Dockerfile.multistage .

$ docker images myapp

REPOSITORY TAG IMAGE ID SIZE myapp bad 3f8c1a9e2b71 1.19GB myapp v2 7a4e0f1c9d83 712MB myapp v3 2b9d4e7f1c05 689MB myapp multistage 9c3a1f8e4b22 247MB

247 Mo — une réduction de 80 % par rapport au point de départ. Et le score dive :

>user@machine: ~/myapp
$ CI=true docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest myapp:multistage

  Using default CI config
Image Source: docker://myapp:multistage
Fetching image... (this can take a while for large images)
Analyzing image...
  efficiency: 0.9971 (99.71%)
  wastedBytes: 4096 bytes (4.1 kB)
  userWastedPercent: 0.29%
Result:PASS [Total:3] [Passed:3] [Failed:0] [Warn:0] [Skipped:0]

99,71 % d'efficacité. Les 4 Ko d'octets « gaspillés » restants sont du bruit de fond — des métadonnées de layer Docker inévitables.

Choisir la bonne base slim

python:3.12-slim est le bon compromis pour la plupart des apps Python. python:3.12-alpine est encore plus petite (environ 20 Mo) mais utilise musl libc au lieu de glibc, ce qui peut poser des problèmes de compatibilité avec les wheels binaires comme numpy ou pandas. Testez avant de vous engager sur Alpine pour une stack orientée données.

Les multi-stage builds sont aussi la bonne approche pour les langages compilés. Si vous construisez une app Go, Rust ou C, le stage builder contient toute la chaîne de compilation, et seul le binaire final atterrit dans le stage de production. Ce qui nous amène à la section bonus.

Bonus — FROM scratch : le minimum absolu​

FROM scratch est un mot-clé Docker spécial — ce n'est pas un nom d'image, c'est un signal indiquant au builder Docker que la nouvelle image doit démarrer complètement vide. Pas de shell, pas de gestionnaire de packages, pas de libc, rien. Juste les fichiers que vous y copiez explicitement avec COPY.

Cela ne fonctionne que si votre application est un binaire autonome sans aucune dépendance externe. Le Go compilé statiquement en est l'exemple parfait :

# syntax=docker/dockerfile:1
FROM golang:1.22-alpine AS builder
WORKDIR /build
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-w -s' -o server .
FROM scratch
COPY --from=builder /build/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

Ce Dockerfile construit une application Go minimale en deux étapes. D'abord, il compile le code Go dans un conteneur Alpine complet, puis il crée une image finale ultra-légère en copiant seulement l'exécutable compilé, sans inclure le système d'exploitation ou les outils inutiles.

Les flags -ldflags '-w -s' retirent les informations de debug et la table des symboles, grignotant quelques Mo de plus sur le binaire.

>user@machine: ~/myserver

$ docker build -t myserver:scratch -f Dockerfile.scratch .

$ docker images myserver:scratch

REPOSITORY TAG IMAGE ID SIZE myserver scratch 1a2b3c4d5e6f 6.88MB

6,88 Mo. Moins de 7 Mo pour un serveur HTTP de production. Et dive ?

>user@machine: ~/myserver
$ CI=true docker run --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest myserver:scratch

  Using default CI config
Image Source: docker://myserver:scratch
Fetching image... (this can take a while for large images)
Analyzing image...
  efficiency: 1.0000 (100.00%)
  wastedBytes: 0 bytes (0 B)
  userWastedPercent: 0.00%
Result:PASS [Total:3] [Passed:3] [Failed:0] [Warn:0] [Skipped:0]

100,00 % d'efficacité. 0 octet gaspillé. Il n'y a littéralement plus rien à améliorer.

FROM scratch n'est pas toujours pratique

Une image scratch n'a aucun shell — docker exec mycontainer sh échouera. Pas de shell signifie pas de scripts shell dans votre entrypoint, pas de ping, pas de curl pour des health checks depuis l'intérieur du container. Pour le debug, envisagez plutôt gcr.io/distroless/static : presque aussi petite que scratch, mais elle inclut une poignée d'outils POSIX et est maintenue par Google pour les correctifs de sécurité.

Dive comme garde-fou qualité en CI​

Lancer dive à la main est utile pendant le développement, mais la vraie puissance est dans le mode --ci — il lit un fichier de configuration et renvoie un code de sortie non nul si votre image ne respecte pas les seuils. Branchez ça directement dans votre pipeline.

Créez un .dive-ci.yaml à la racine de votre projet :

rules:
lowestEfficiency: 0.95
highestWastedBytes: "20mb"
highestUserWastedPercent: 0.05

Ce fichier de configuration définit des règles de seuils pour l'analyse d'efficacité et de gaspillage de ressources. Les paramètres établissent qu'une efficacité minimale de 95% est acceptable, qu'au maximum 20 mégaoctets peuvent être gaspillés, et que le gaspillage ne doit pas dépasser 5% de l'utilisation totale de l'utilisateur.

Ajoutez ensuite une étape dans votre pipeline CI :

>CI pipeline step

$ DIVE_CONFIG=.dive-ci.yaml CI=true docker run --rm
-v /var/run/docker.sock:/var/run/docker.sock
-v $(pwd)/.dive-ci.yaml:/.dive-ci.yaml
-e DIVE_CONFIG=/.dive-ci.yaml
wagoodman/dive:latest myapp:latest

Si l'efficacité de l'image tombe sous 95 % ou que les octets gaspillés dépassent 20 Mo, l'étape échoue et le déploiement n'a jamais lieu. Plutôt cool, non ?

Quels seuils ont du sens ?

lowestEfficiency: 0.95 et highestWastedBytes: "20mb" sont des valeurs de départ raisonnables. Augmentez highestWastedBytes si votre image contient légitimement de gros assets statiques (polices, modèles, jeux de données) qu'on ne peut pas séparer proprement.

Claude Code — automatiser toute la boucle​

Tout ce que nous avons fait à la main — lancer dive, lire le JSON, repérer les layers gaspilleurs, décider quoi corriger, éditer le Dockerfile, reconstruire, relancer — est une boucle répétable. Si vous utilisez Claude Code, vous pouvez piloter l'ensemble avec une seule commande slash :

/docker-dive-optimization

Elle détecte automatiquement le Dockerfile principal du projet (ou accepte un chemin ou un tag d'image en argument), construit l'image, exécute dive en mode JSON, et classe chaque constat dans trois catégories — qui correspondent directement aux patterns que nous avons appliqués à la main dans cet article.

Catégorie A — corriger tout de suite : du gaspillage mécanique, indiscutablement erroné. Un apt-get clean placé dans un RUN distinct de l'installation, un --no-cache absent d'un apk add, un répertoire de cache non nettoyé dans le layer qui l'a créé. Claude applique la modification du Dockerfile, reconstruit, et relance dive pour vérifier le gain avant d'annoncer le succès.

Catégorie B — proposer et attendre : les constats dont la bonne réponse demande un jugement. Passer de python:3.12 à python:3.12-slim dans un stage de production. Introduire un multi-stage build là où il n'y en a pas. Remplacer un runtime de 300 Mo présent uniquement pour un seul appel CLI par un binaire autonome. Claude présente les preuves au niveau des layers et le compromis, puis attend votre décision avant de toucher à quoi que ce soit.

Catégorie C — TODOs : tout ce qui nécessite des recherches avant d'y toucher. Consigné sous forme d'éléments numérotés dans .todos/ pour ne rien perdre entre deux sessions.

Cibler n'importe quelle image ou n'importe quel Dockerfile

La commande utilise par défaut le Dockerfile du projet, mais elle accepte directement un chemin ou un tag d'image :

/docker-dive-optimization myapp:latest
/docker-dive-optimization ./services/api/Dockerfile

Vous pouvez copier le fichier de commande dans le répertoire .claude/commands/ de n'importe quel projet — il détecte automatiquement s'il faut utiliser docker compose build ou un simple docker build, donc aucun réglage spécifique au projet n'est nécessaire.

Cette répartition en trois catégories reflète les arbitrages de cet article. Nettoyer le cache apt dans le même layer, c'est la catégorie A — aucune discussion, on merge et on vérifie. Passer à un multi-stage build, c'est la catégorie B — de vrais compromis à examiner d'abord. Remplacer un binaire de niche que vous n'avez jamais audité, c'est la catégorie C — recherche avant d'y toucher. La même analyse que celle faite à la main, automatisée.

Pour la preuve : j'ai lancé /docker-dive-optimization sur le blog même que vous êtes en train de lire. L'image du devcontainer avait gonflé à 2,5 Go, affichait 89,95 % d'efficacité et transportait 433 Mo d'octets gaspillés — hérités en grande partie d'une base lourde mcr.microsoft.com/devcontainers/javascript-node:20-bookworm qui embarquait silencieusement oh-my-zsh, subversion et une chaîne de compilation C/C++ complète dont personne n'avait besoin. La catégorie A a attrapé un chown -R redondant qui forçait l'overlay FS de Docker à copier 1 410 fichiers dans un layer inutile de 21 Mo. La catégorie B a signalé l'image de base et les 996 Mo de node_modules intégrés à chaque build.

résultat dive avant et après optimisation sur ce blog

De 2,5 Go à 735 Mo — une réduction de 70 %, avec l'efficacité passée de 89,95 % à 98,75 %. Le simple remplacement de l'image de base spécifique aux devcontainers par node:20-bookworm-slim a économisé plus d'un gigaoctet. Déplacer node_modules vers un volume Docker nommé à l'exécution a récupéré 996 Mo de plus. Toute la session — reconstruction et relance de dive pour vérifier comprises — a pris moins de quinze minutes.

Récapitulatif​

Voici la progression que nous avons suivie :

Le parcours en quatre étapes

  • Départ (Dockerfile.bad) — 1,19 Go, 61 % d'efficacité : un RUN apt-get update séparé, aucun nettoyage de cache, des outils de build dans l'image finale.
  • v2 — 712 Mo, 98 % d'efficacité : nettoyer le cache et les listes apt dans la même instruction RUN qui les installe.
  • v3 — 689 Mo, 98 % d'efficacité : regrouper toutes les étapes d'installation dans une seule chaîne RUN, supprimer les fichiers .pyc.
  • Multi-stage — 247 Mo, 99,7 % d'efficacité : utiliser un stage builder séparé ; seuls les packages installés passent dans l'image de production slim.
  • Bonus (FROM scratch) — 6,88 Mo, 100 % d'efficacité : binaire compilé statiquement, aucune image de base.

Conclusion​

Il existe une version de cette histoire où vous expédiez une image de 1,2 Go en production parce que ça marche — et vous ne vous retournez jamais. J'y suis passé. L'app tourne, l'équipe livre, personne ne se plaint. Jusqu'à ce que vous regardiez la facture de stockage de votre registry, ou votre temps de démarrage à froid sur une plateforme serverless, ou le temps de pull sur un runner CI avec un cache vide.

dive rend l'invisible visible. Une fois que vous avez vu 438 Mo d'octets gaspillés surlignés en rouge, impossible de l'oublier. Les correctifs n'ont rien d'exotique : nettoyez dans le même layer, combinez vos instructions RUN, utilisez des multi-stage builds. Ce sont des choses que tout praticien de Docker devrait connaître par cœur, et vous avez maintenant un outil qui vous dira immédiatement si vous en avez oublié une.

Pour plus de contexte sur l'écriture de Dockerfiles dignes de la production, One Docker Image for Production and Devcontainers couvre l'approche source unique de vérité où un seul Dockerfile gère proprement les deux environnements. Et si vous cherchez un autre outil Docker orienté terminal, il y a un article compagnon sur lazydocker — un dashboard TUI complet pour gérer vos containers et vos logs sans quitter le terminal.


Cet article vous a-t-il été utile ?
Avez-vous suivi les étapes de cet article ?