Aller au contenu
Conteneurs & Orchestration medium

Analysez vos images Docker avec Dive

20 min de lecture

Interface de Dive

Dive vous montre exactement ce qui se passe dans chaque couche de vos images Docker. En 5 minutes, vous saurez identifier les fichiers qui gaspillent de l'espace, comprendre pourquoi votre image est volumineuse, et intégrer une validation automatique dans vos pipelines CI/CD.

  • Explorer les couches : naviguer dans la structure interne d'une image avec l'interface TUI
  • Identifier l'espace gaspillé : repérer les fichiers dupliqués ou inutiles entre couches
  • Valider en CI/CD : bloquer les builds si l'efficacité est insuffisante
  • Exporter en JSON : intégrer les métriques dans vos outils de monitoring

Dive ne modifie ni ne reconstruit vos images : c'est un outil de lecture. Il ouvre l'image couche par couche et rapporte ce qu'il y trouve, à vous d'en tirer les corrections dans le Dockerfile. Deux usages cohabitent : l'exploration interactive, quand vous cherchez à comprendre, et le mode CI, qui rend un simple verdict de réussite ou d'échec.

FonctionnalitéDescription
Exploration TUIInterface terminal interactive pour naviguer dans les couches
Analyse d'efficacitéCalcul du pourcentage d'espace gaspillé
Mode CIValidation automatique avec seuils configurables
Export JSONDonnées structurées pour intégration externe
Multi-sourcesSupport Docker, Podman et archives

Dive est un binaire Go autonome, sans dépendance à installer. Il a en revanche besoin d'accéder au moteur de conteneurs pour extraire les images : appartenance au groupe docker ou socket Podman accessible. La version est épinglée dans chaque commande, ce qui rend l'installation reproductible et permet de vérifier l'empreinte du fichier téléchargé.

Le dépôt publie un fichier dive_<version>_checksums.txt qui contient l'empreinte SHA-256 de chaque artefact. Téléchargez-le en même temps que le paquet et refusez l'installation si la vérification échoue : c'est le seul moyen de savoir que le fichier reçu est bien celui publié par le projet.

Fenêtre de terminal
# Télécharger le paquet ET le fichier de sommes officiel (v0.13.1)
DIVE_VERSION="0.13.1"
DIVE_BASE="https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}"
curl -fsSLO "${DIVE_BASE}/dive_${DIVE_VERSION}_linux_amd64.deb"
curl -fsSLO "${DIVE_BASE}/dive_${DIVE_VERSION}_checksums.txt"
# Vérifier l'empreinte, puis seulement installer
sha256sum --ignore-missing --check "dive_${DIVE_VERSION}_checksums.txt"
sudo dpkg -i "dive_${DIVE_VERSION}_linux_amd64.deb"

La commande de vérification doit afficher dive_0.13.1_linux_amd64.deb: Réussi (ou OK en anglais). L'option --ignore-missing est nécessaire car le fichier de sommes couvre toutes les architectures publiées, dont vous n'avez téléchargé qu'un artefact.

Pour les distributions RPM (Fedora, RHEL), la démarche est identique, seule l'extension change :

Fenêtre de terminal
DIVE_VERSION="0.13.1"
DIVE_BASE="https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}"
curl -fsSLO "${DIVE_BASE}/dive_${DIVE_VERSION}_linux_amd64.rpm"
curl -fsSLO "${DIVE_BASE}/dive_${DIVE_VERSION}_checksums.txt"
sha256sum --ignore-missing --check "dive_${DIVE_VERSION}_checksums.txt"
sudo rpm -i "dive_${DIVE_VERSION}_linux_amd64.rpm"

Vérification de l'installation :

Fenêtre de terminal
dive --version

Résultat attendu :

dive 0.13.1

Une TUI (Terminal User Interface) est une interface graphique dessinée dans le terminal, pilotée au clavier. Celle de Dive met en regard deux informations qu'on ne peut pas croiser autrement : la commande du Dockerfile qui a produit une couche et les fichiers que cette commande a réellement écrits. C'est là que les surprises apparaissent, un apt-get install qui laisse 200 Mo de cache par exemple.

L'image doit déjà être présente localement : Dive ne la télécharge pas. L'analyse prend quelques secondes, plus longtemps sur une image volumineuse car l'outil extrait chaque couche.

Fenêtre de terminal
dive nginx:1.27.3-alpine

L'interface s'ouvre avec deux panneaux principaux :

  • Panneau gauche : liste des couches avec leur taille et la commande Dockerfile associée
  • Panneau droit : arborescence des fichiers de la couche sélectionnée

Deux raccourcis changent la façon de lire l'image et méritent d'être retenus en premier. Ctrl+Space bascule entre la vue d'une couche seule et la vue agrégée, qui montre le système de fichiers tel qu'il sera au démarrage du conteneur. Ctrl+B bascule l'affichage des fichiers inchangés, ce qui permet d'isoler l'apport réel de la couche sélectionnée.

ToucheAction
TabBasculer entre les panneaux
/ ou k / jNaviguer dans les listes
SpaceDéplier/replier un dossier
Ctrl+U / Ctrl+DPage haut / Page bas
Ctrl+AAfficher les fichiers ajoutés
Ctrl+RAfficher les fichiers supprimés
Ctrl+MAfficher les fichiers modifiés
Ctrl+BAfficher tous les fichiers non modifiés
Ctrl+SpaceBasculer entre vue couche / vue agrégée
Ctrl+LAfficher les détails de la couche
Ctrl+FFiltrer les fichiers
qQuitter

Dans l'arborescence des fichiers, Dive utilise des codes couleur :

CouleurSignification
VertFichier ajouté dans cette couche
RougeFichier supprimé dans cette couche
JauneFichier modifié dans cette couche
BlancFichier inchangé (hérité des couches précédentes)

Au-delà de l'exploration visuelle, Dive calcule des métriques d'efficacité. Le principe est simple : un fichier écrit dans une couche puis réécrit ou supprimé dans une couche ultérieure occupe quand même la place des deux versions, car une couche ne peut jamais être modifiée après coup. C'est ce que Dive appelle l'espace gaspillé, et ce chiffre est comparable d'une construction à l'autre.

Ces trois valeurs se lisent ensemble. L'efficiency donne l'impression générale, mais le userWastedPercent est le plus actionnable : il ne compte que le gaspillage causé par vos propres instructions, en écartant celui hérité de l'image de base sur laquelle vous ne pouvez rien.

MétriqueDescriptionSeuil recommandé
efficiencyPourcentage d'espace utilisé efficacement≥ 90%
wastedBytesOctets gaspillés (fichiers dupliqués/supprimés)≤ 10 MB
userWastedPercentPourcentage d'espace gaspillé par les modifications utilisateur≤ 10%

L'option --ci désactive l'interface interactive et écrit le rapport sur la sortie standard. Sans fichier de configuration, Dive applique ses règles par défaut, d'où la ligne Using default CI config en tête de sortie.

Fenêtre de terminal
dive nginx:1.27.3-alpine --ci

Résultat :

Using default CI config
Image Source: docker://nginx:1.27.3-alpine
Extracting image from docker-engine... (this can take a while for large images)
Analyzing image...
efficiency: 99.1303 %
wastedBytes: 657611 bytes (658 kB)
userWastedPercent: 1.6763 %
Inefficient Files:
Count Wasted Space File Path
2 445 kB /etc/ssl/certs/ca-certificates.crt
3 160 kB /lib/apk/db/installed
3 49 kB /lib/apk/db/scripts.tar
2 1.5 kB /etc/passwd
2 1.0 kB /etc/group
2 547 B /etc/shadow
3 414 B /lib/apk/db/triggers
3 396 B /etc/apk/world
Results:
PASS: highestUserWastedPercent
SKIP: highestWastedBytes: rule disabled
PASS: lowestEfficiency
Result:PASS [Total:3] [Passed:2] [Failed:0] [Warn:0] [Skipped:1]

La section "Inefficient Files" montre les fichiers qui apparaissent dans plusieurs couches (dupliqués) ou qui ont été supprimés après avoir été ajoutés.

Dans l'exemple ci-dessus :

  • ca-certificates.crt apparaît 2 fois → 445 kB gaspillés
  • Les fichiers APK (installed, scripts.tar) sont modifiés à chaque apk add

En mode --ci, Dive termine avec un code de sortie non nul dès qu'une règle échoue, ce qui fait tomber le job qui l'exécute. Vous transformez ainsi une bonne pratique en contrainte vérifiée à chaque build. Commencez par des seuils proches de ce que produisent déjà vos images : un seuil trop ambitieux d'emblée casse la chaîne et pousse l'équipe à désactiver la vérification.

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

.dive-ci.yaml
rules:
# Efficacité minimale (ratio 0-1)
lowestEfficiency: 0.9
# Pourcentage max de gaspillage utilisateur (ratio 0-1)
highestUserWastedPercent: 0.1
# Octets max gaspillés (disabled = pas de limite)
highestWastedBytes: disabled

Le job d'analyse tourne dans l'image officielle de Dive et doit atteindre le même démon Docker que le job de build, d'où le service dind et la variable DOCKER_HOST. Le allow_failure: false est explicite à dessein : sans lui, la valeur par défaut du job resterait implicite alors que c'est justement le point qui décide si le pipeline s'arrête.

.gitlab-ci.yml
stages:
- build
- analyze
build-image:
stage: build
image: docker:27.5.1@sha256:aa3df78ecf320f5fafdce71c659f1629e96e9de0968305fe1de670e0ca9176ce
services:
- docker:27.5.1-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
analyze-image:
stage: analyze
image: wagoodman/dive:v0.13.1@sha256:f1886e6c32c094fc41a623c1989f5cb3e48aa766da5f0be233f911fc1d85ce10
services:
- docker:27.5.1-dind
variables:
DOCKER_HOST: tcp://docker:2375
script:
- dive $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --ci
allow_failure: false

Sur GitHub Actions, Docker est déjà présent sur les runners hébergés : il n'y a pas de service à déclarer. Trois précautions sont appliquées ici. L'action checkout est épinglée par SHA avec son tag en commentaire, pour qu'une republication du tag ne change pas le code exécuté. Le workflow part de permissions: {} et le job ne récupère que contents: read. Enfin, l'empreinte du paquet Dive est vérifiée avant installation, exactement comme sur un poste de travail.

.github/workflows/analyze.yml
name: Analyze Docker Image
on:
push:
branches: [main]
permissions: {}
jobs:
analyze:
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Récupérer le code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Build image
run: docker build -t "myapp:${GITHUB_SHA}" .
- name: Install Dive
env:
DIVE_VERSION: "0.13.1"
run: |
base="https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}"
curl -fsSLO "${base}/dive_${DIVE_VERSION}_linux_amd64.deb"
curl -fsSLO "${base}/dive_${DIVE_VERSION}_checksums.txt"
sha256sum --ignore-missing --check "dive_${DIVE_VERSION}_checksums.txt"
sudo dpkg -i "dive_${DIVE_VERSION}_linux_amd64.deb"
- name: Analyze image
run: dive "myapp:${GITHUB_SHA}" --ci

Vous pouvez surcharger les règles sans fichier de configuration :

Fenêtre de terminal
dive myimage:1.0.0 --ci \
--lowestEfficiency 0.95 \
--highestUserWastedPercent 0.05 \
--highestWastedBytes 5MB

Le mode --ci répond par oui ou non ; l'export JSON conserve le détail chiffré. C'est ce qu'il faut pour suivre l'évolution de la taille d'une image au fil des versions, ou pour alimenter un tableau de bord. Le fichier est écrit à l'emplacement passé en argument, et l'interface interactive ne s'ouvre pas.

Fenêtre de terminal
dive nginx:1.27.3-alpine --json /tmp/dive-report.json

Le fichier JSON contient :

  • La liste des couches avec leurs fichiers
  • Les métriques d'efficacité
  • Les fichiers inefficaces identifiés

Exemple d'extraction avec jq :

Fenêtre de terminal
# Extraire les métriques globales
cat /tmp/dive-report.json | jq '{
efficiency: .image.efficiency,
sizeBytes: .image.sizeBytes,
wastedBytes: .image.inefficientBytes
}'

Dive ne se limite pas au démon Docker. Le préfixe placé devant le nom de l'image indique où aller la chercher : moteur Docker, moteur Podman ou fichier d'archive. Cette dernière possibilité est précieuse en CI, où l'image peut être analysée sans qu'aucun moteur de conteneurs ne tourne.

C'est le comportement par défaut : sans préfixe, Dive interroge le démon Docker désigné par DOCKER_HOST.

Fenêtre de terminal
dive myimage:1.0.0
# ou explicitement
dive docker://myimage:1.0.0

Le préfixe podman:// s'adresse au stockage local de Podman, sans passer par un démon. L'utilisateur qui lance la commande doit être celui qui possède les images, les conteneurs rootless étant propres à chaque compte.

Fenêtre de terminal
dive podman://myimage:1.0.0

docker save produit une archive contenant toutes les couches. Dive sait la lire directement, ce qui permet d'analyser une image construite ailleurs ou récupérée en tant qu'artefact de pipeline.

Fenêtre de terminal
# Exporter avec docker save
docker save myimage:1.0.0 -o myimage.tar
# Analyser l'archive
dive docker-archive://myimage.tar

Une fois les problèmes identifiés avec Dive, voici les actions correctives :

Chaque instruction RUN crée une couche. Trois apk add successifs réécrivent donc trois fois la base de données des paquets, et ce sont ces réécritures que Dive signale dans les fichiers inefficaces. Une seule instruction produit une seule version de chaque fichier.

# ❌ Mauvais : 3 couches, fichiers APK dupliqués
RUN apk add curl
RUN apk add jq
RUN apk add git
# ✅ Bon : 1 couche, moins de gaspillage
RUN apk add --no-cache curl jq git

C'est le piège le plus contre-intuitif : un rm -rf dans une couche ultérieure n'allège rien. Le fichier reste stocké dans la couche qui l'a créé, la suppression ne fait qu'ajouter une marque qui le masque. Le nettoyage doit avoir lieu dans la même instruction que la création, ou mieux, ne jamais écrire le cache grâce à --no-cache.

# ❌ Mauvais : le cache APK reste dans une couche précédente
RUN apk update
RUN apk add curl
RUN rm -rf /var/cache/apk/*
# ✅ Bon : tout dans la même couche
RUN apk add --no-cache curl

Le multi-stage est la technique la plus efficace quand la compilation demande des outils absents en production. Seuls les fichiers explicitement recopiés avec COPY --from se retrouvent dans l'image finale : le compilateur, les sources et les dépendances de build restent dans l'étape de construction et disparaissent.

# Stage build
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Stage runtime (image finale légère)
FROM alpine:3.20.6@sha256:de4fe7064d8f98419ea6b49190df1abbf43450c1702eeb864fe9ced453c1cc5f
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]

Pour plus de techniques d'optimisation, consultez le guide Optimiser la taille des images.

Presque tous les problèmes rencontrés avec Dive relèvent de la même cause : l'outil n'atteint pas le moteur de conteneurs, ou n'y trouve pas l'image demandée. Les cas ci-dessous couvrent les quatre situations les plus fréquentes, de la plus courante à la plus spécifique.

Vérifiez que Docker est démarré et que l'utilisateur a les permissions :

Fenêtre de terminal
# Vérifier le daemon
docker info
# Ajouter l'utilisateur au groupe docker
sudo usermod -aG docker $USER
newgrp docker

L'image doit être présente localement. Téléchargez-la d'abord :

Fenêtre de terminal
docker pull nginx:1.27.3-alpine
dive nginx:1.27.3-alpine

Vérifiez que votre terminal supporte UTF-8 et les 256 couleurs :

Fenêtre de terminal
export TERM=xterm-256color
dive myimage:1.0.0

Dive ne trouve pas le socket Docker (macOS/Colima)

Section intitulée « Dive ne trouve pas le socket Docker (macOS/Colima) »

Dive s'adresse au socket désigné par DOCKER_HOST et ne devine pas les emplacements alternatifs. Avec Colima ou Podman, ce chemin n'est pas celui par défaut : il faut l'exporter dans le terminal, ou dans votre fichier de configuration de shell pour ne plus y revenir.

Fenêtre de terminal
export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock"
# ou pour Podman
export DOCKER_HOST="unix://${XDG_RUNTIME_DIR}/podman/podman.sock"
  • Interface TUI : dive <image> pour explorer visuellement les couches
  • Mode CI : dive <image> --ci pour validation automatique dans les pipelines
  • Efficacité ≥ 90% : seuil recommandé pour des images optimisées
  • Combiner les RUN : évite la duplication de fichiers entre couches
  • Multi-stage builds : séparent build et runtime pour des images légères

Les questions ci-dessous reviennent le plus souvent lors des premières utilisations de Dive : lecture des métriques d'efficacité, analyse sans démon Docker, intégration en CI/CD et différence avec docker history.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn