docker history und dive: Image-Layer analysieren und Speicherplatz sparen
AI generated
FROM
RUN
Docker · Performance · CI/CD
docker history und dive
Layer-fuer-Layer verstehen, wo der Speicherplatz hingeht

Ein Image wächst über Monate unbemerkt von 200 Megabyte auf über ein Gigabyte, und niemand kann auf Anhieb sagen, welcher Build-Schritt dafür verantwortlich ist. docker history liefert einen ersten groben Überblick, das Tool dive zeigt exakt, welche Datei in welchem Layer wie viel Platz beansprucht.

16 Min. Lesezeit docker history dive Layer-Caching Image-Optimierung

1. Warum Image-Größe überhaupt eine Rolle spielt

Ein zu großes Docker-Image ist nicht nur eine kosmetische Unschönheit, sondern hat handfeste Auswirkungen auf mehrere Ebenen gleichzeitig: längere Push- und Pull-Zeiten in CI/CD-Pipelines, langsamere Deployment-Zyklen gerade bei häufigen Releases, höherer Speicherplatzbedarf in der Registry, und im Fall von Auto-Scaling-Szenarien spürbar längere Zeit, bis eine neue Instanz überhaupt startbereit ist, weil das Image erst vollständig heruntergeladen werden muss, bevor der Container starten kann.

Jedes Docker-Image besteht aus einer Abfolge von Layern, wobei jede Anweisung im Dockerfile, die das Dateisystem verändert, wie RUN, COPY oder ADD, einen neuen, unveränderlichen Layer erzeugt. Diese Layer werden übereinandergestapelt und über ein Union-Filesystem wie OverlayFS zu einer einzigen, konsistenten Sicht zusammengeführt. Das Problem dabei: Einmal in einem Layer geschriebene Daten bleiben Teil der Image-Größe, selbst wenn sie in einem späteren Layer wieder gelöscht werden, weil gelöschte Dateien nur als sogenannte Whiteout-Marker markiert, aber nicht physisch aus vorherigen Layern entfernt werden.

2. docker history als erster Überblick

Der Befehl docker history ist in jeder Docker-Installation bereits vorhanden und liefert ohne zusätzliche Installation eine Liste aller Layer eines Images, zusammen mit dem Befehl, der den jeweiligen Layer erzeugt hat, und der Größe, die dieser Layer zur Gesamt-Image-Größe beiträgt. Das macht ihn zum naheliegenden ersten Schritt, wenn ein Image unerwartet groß ist: Ein Blick auf die Größenspalte zeigt sofort, ob ein einzelner Layer auffällig aus dem Rahmen fällt, etwa ein RUN apt-get install-Befehl, der mehrere hundert Megabyte beiträgt.

Standardmäßig kürzt docker history den ausgeführten Befehl auf eine feste Zeichenlänge, was bei komplexen, mehrzeiligen RUN-Befehlen mit vielen verketteten Kommandos schnell unübersichtlich wird. Die Option --no-trunc zeigt den vollständigen Befehl jedes Layers an, was bei der Fehlersuche fast immer die bessere Wahl ist, auch wenn die Ausgabe dadurch deutlich breiter und für das Terminal unhandlicher wird.


# Ueberblick ueber alle Layer eines Images
docker history myapp:latest

# Vollstaendige Befehle ohne Kuerzung anzeigen
docker history --no-trunc myapp:latest

# Nur Groesse und Befehl, ohne Kopfzeile, fuer Skripting
docker history --no-trunc --format '{{.Size}}\t{{.CreatedBy}}' myapp:latest

3. Wo docker history an seine Grenzen stößt

So nützlich docker history als erster Überblick ist, es zeigt nur die Größe pro Layer als Ganzes, nicht welche einzelnen Dateien innerhalb dieses Layers wie viel Platz beanspruchen. Ein Layer mit 300 Megabyte könnte aus einer einzigen großen Binärdatei bestehen oder aus tausenden kleinen Dateien, docker history macht hier keinen Unterschied sichtbar. Genau diese fehlende Detailtiefe ist der Grund, warum spezialisierte Werkzeuge wie dive entstanden sind.

Ein zweites, subtileres Problem betrifft gelöschte Dateien: Wenn ein Dockerfile in einem RUN-Befehl zunächst ein großes Archiv herunterlädt, entpackt und anschließend wieder löscht, taucht dieser komplette Vorgang nur als ein einziger Layer mit der Netto-Größendifferenz auf, sofern Download, Entpacken und Löschen im selben RUN-Befehl passieren. Werden diese Schritte dagegen über mehrere separate RUN-Befehle verteilt, bleibt das heruntergeladene Archiv im ersten Layer vollständig erhalten, selbst wenn ein späterer Layer es augenscheinlich wieder entfernt, weil Whiteout-Marker die Datei nur im finalen Dateisystem verstecken, nicht aus dem Layer selbst löschen.

4. dive installieren

Das Tool dive, ein Open-Source-Projekt von Alex Goodman, ist eigens dafür entwickelt worden, diese Lücke zu schließen: Es lädt ein Image, entpackt jeden Layer virtuell und stellt eine interaktive, dateibaumartige Ansicht bereit, in der jede einzelne Datei mit ihrer Größe und dem Layer, in dem sie hinzugefügt oder verändert wurde, sichtbar ist. Die Installation erfolgt je nach Betriebssystem über den jeweiligen Paketmanager oder direkt über die Binärdatei aus dem GitHub-Release.

Auf Debian- und Ubuntu-basierten Systemen lässt sich dive über ein .deb-Paket installieren, auf macOS über Homebrew, und für andere Linux-Distributionen steht ein statisch gelinktes Binary zur Verfügung, das ohne weitere Abhängigkeiten läuft. Da dive selbst mit der lokalen Docker-Engine kommuniziert, um Images zu laden, muss es auf derselben Maschine laufen, auf der auch der Docker-Daemon aktiv ist, oder zumindest Zugriff auf denselben Docker-Socket haben.


# Installation auf Debian/Ubuntu ueber das offizielle .deb-Paket
DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" \
  | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
curl -OL https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.deb
sudo apt install ./dive_${DIVE_VERSION}_linux_amd64.deb

# Installation auf macOS
brew install dive

# Version pruefen
dive --version

5. dive interaktiv nutzen

Der Aufruf dive myapp:latest öffnet eine Terminal-Oberfläche mit zwei Hauptbereichen: links die Liste aller Layer mit ihrer jeweiligen Größe, rechts der Dateibaum des aktuell ausgewählten Layers. Mit den Pfeiltasten lässt sich zwischen Layern wechseln, während der Dateibaum rechts entsprechend aktualisiert wird und farblich markiert, welche Dateien in diesem Layer neu hinzugefügt, verändert oder gelöscht wurden, was die Ursache einer Größenzunahme meist innerhalb weniger Sekunden sichtbar macht.

Besonders wertvoll ist die eingebaute Effizienz-Bewertung, die dive automatisch berechnet und als Prozentzahl im Kopfbereich anzeigt: Sie setzt die Summe der tatsächlich genutzten, einzigartigen Dateigröße ins Verhältnis zur Gesamtgröße aller Layer und macht damit sofort sichtbar, wie viel Speicherplatz durch mehrfach überschriebene oder wieder gelöschte Dateien effektiv verschwendet wird. Ein Wert deutlich unter 90 Prozent ist ein starkes Signal, dass sich eine genauere Untersuchung der Layer-Struktur lohnt.


# Interaktive Analyse eines lokalen Images starten
dive myapp:latest

# Wichtigste Tastaturkuerzel innerhalb von dive:
#   Tab        zwischen Layer-Liste und Dateibaum wechseln
#   Pfeiltasten  durch Layer bzw. Dateien navigieren
#   Ctrl+A       nur geaenderte Dateien im aktuellen Layer anzeigen
#   Ctrl+U       nur ungenutzte/verschwendete Dateien anzeigen
#   Ctrl+F       im Dateibaum suchen

6. dive automatisiert in der CI-Pipeline

Über die interaktive Nutzung hinaus unterstützt dive einen nicht-interaktiven CI-Modus, der über die Umgebungsvariable CI=true aktiviert wird. In diesem Modus prüft dive das Image gegen konfigurierbare Schwellenwerte, etwa eine Mindest-Effizienz oder einen maximalen Anteil verschwendeten Speicherplatzes, und beendet sich mit einem Fehler-Exit-Code, sobald diese Schwellenwerte unterschritten werden, was sich direkt als Quality Gate in eine Build-Pipeline einbauen lässt.

Die Konfiguration erfolgt über eine .dive-ci-Datei im YAML-Format, in der sich sowohl der minimal geforderte Effizienzwert als auch die maximal erlaubte Menge verschwendeten Speicherplatzes in Bytes festlegen lässt. Damit wird Image-Größe zu einer messbaren, automatisch überwachten Kennzahl, ähnlich wie Testabdeckung oder Linter-Warnungen, statt einer Eigenschaft, die nur gelegentlich manuell und meist zu spät auffällt, oft erst wenn die Registry-Kosten oder die Deployment-Zeiten bereits spürbar gestiegen sind.


# .dive-ci: Schwellenwerte fuer den CI-Modus
rules:
  lowestEfficiency: 0.9
  highestWastedBytes: 50MB
  highestUserWastedPercent: 0.1

7. Häufige Ursachen für verschwendeten Speicherplatz

Die mit Abstand häufigste Ursache für aufgeblähte Images sind Paketmanager-Caches, die nach der Installation nicht bereinigt werden: Ein apt-get install ohne anschließendes rm -rf /var/lib/apt/lists/* im selben RUN-Befehl hinterlässt den kompletten Paketindex im Layer, oft mehrere hundert Megabyte, die nach der Installation keinerlei Nutzen mehr haben. Dasselbe Muster tritt bei npm install ohne --production-Flag, bei nicht bereinigten pip-Caches und bei temporären Build-Artefakten auf, die im finalen Image nie gebraucht werden.

Ein zweiter häufiger Fehler ist eine ungünstige Reihenfolge der Dockerfile-Anweisungen, insbesondere ein COPY . . für den gesamten Quellcode, bevor Abhängigkeiten installiert werden. Das führt zwar nicht direkt zu einem größeren Image, zerstört aber den Layer-Cache bei jeder noch so kleinen Code-Änderung, wodurch teure Installationsschritte bei jedem Build erneut ausgeführt werden müssen, was die Build-Zeit unnötig in die Länge zieht und in Summe auch mehr temporäre Layer während der Entwicklung erzeugt.

8. Ein Dockerfile im Vorher-Nachher-Vergleich

Die Kombination aus Multi-Stage-Builds und sorgfältiger Cache-Bereinigung innerhalb desselben RUN-Befehls löst die meisten der zuvor beschriebenen Probleme gleichzeitig. Beim Multi-Stage-Build werden Build-Werkzeuge und Zwischenartefakte in einer separaten Build-Stage installiert, die im finalen Image gar nicht erst auftaucht, während im letzten Stage nur die tatsächlich benötigten Laufzeit-Artefakte kopiert werden, ohne jemals einen Layer mit Compiler oder Build-Abhängigkeiten im finalen Image zu hinterlassen.

Ein konkretes Beispiel zeigt den Unterschied deutlich: Statt Paketinstallation und Cache-Bereinigung in getrennten RUN-Befehlen unterzubringen, wodurch der Cache im ersten Layer erhalten bliebe, werden beide Schritte über && im selben RUN-Befehl verkettet, sodass nur die Netto-Größendifferenz in den finalen Layer eingeht. Kombiniert mit einem Multi-Stage-Build reduziert dieses Muster die Image-Größe in der Praxis häufig um 50 Prozent oder mehr, ganz ohne Funktionalität einzubüßen.


# Vorher: Cache bleibt erhalten, kein Multi-Stage-Build
FROM node:20
COPY . .
RUN apt-get update
RUN apt-get install -y build-essential
RUN npm install
RUN npm run build

# Nachher: Cache-Bereinigung im selben RUN-Befehl, Multi-Stage-Build
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

9. Der praktische Analyse-Workflow im Zusammenspiel

In der Praxis ergänzen sich beide Werkzeuge in einem klaren, wiederholbaren Ablauf: docker history --no-trunc liefert den schnellen ersten Überblick und zeigt, welcher Build-Schritt für den größten einzelnen Layer verantwortlich ist. Fällt dabei ein auffällig großer Layer auf, folgt die tiefere Analyse mit dive, um exakt zu bestimmen, welche Dateien innerhalb dieses Layers den Platz beanspruchen und ob es sich um tatsächlich benötigte Laufzeit-Dateien oder um vermeidbaren Cache- und Build-Ballast handelt.

Nach der Optimierung des Dockerfiles lohnt sich ein erneuter Durchlauf mit beiden Werkzeugen, um den Effekt zu verifizieren, idealerweise ergänzt um einen dauerhaften dive-CI-Check, der zukünftige Regressionen automatisch verhindert, bevor ein aufgeblähtes Image überhaupt in die Registry gepusht wird. Die folgende Tabelle stellt beide Werkzeuge mit ihren jeweiligen Stärken gegenüber, als Entscheidungshilfe für den nächsten Image-Optimierungs-Durchlauf.

Werkzeug Detailgrad Interaktiv Typischer Einsatz
docker history Pro Layer, ganze Zahl Nein, reine Textausgabe Schneller erster Überblick
dive (interaktiv) Pro Datei innerhalb jedes Layers Ja, Terminal-UI Tiefe manuelle Analyse
dive (CI-Modus) Effizienz- und Waste-Kennzahlen Nein, Exit-Code als Gate Automatisierte Qualitätssicherung
Multi-Stage-Build Strukturelle Vermeidung, kein Analysewerkzeug Nein, Teil des Dockerfiles Dauerhafte Lösung statt Symptombekämpfung

Mironsoft

Container-Infrastruktur, CI-Pipelines und Deployment-Automatisierung

Docker-Setups, die im Team und in Produktion tragfähig bleiben?

Wir prüfen bestehende Dockerfiles und Compose-Stacks auf Sicherheitslücken, aufgeblähte Images und fragile Build-Pipelines und bauen daraus eine Container-Infrastruktur, die schnell baut, sicher läuft und im Team nachvollziehbar bleibt.

Dockerfile-Review

Multi-Stage-Builds, Layer-Caching und Image-Größe systematisch optimieren.

Security-Audit

Container-Isolation, Secrets-Handling und Image-Scanning gegen echte Angriffsflächen absichern.

CI/CD-Integration

Build-Pipelines, Registries und Deployment-Strategien für reproduzierbare Releases aufbauen.

10. Zusammenfassung

Image-Layer-Analyse mit dive: Das Wichtigste auf einen Blick

Erster Schritt

docker history --no-trunc zeigt schnell, welcher Layer am meisten Platz beansprucht.

Tiefenanalyse

dive zeigt einzelne Dateien pro Layer und berechnet eine Effizienz-Kennzahl.

Häufigste Ursache

Nicht bereinigte Paketmanager-Caches, die im selben RUN-Befehl entfernt werden sollten.

Nachhaltige Lösung

Multi-Stage-Builds kombiniert mit dive als automatisiertes CI-Quality-Gate.

11. FAQ: Image-Layer-Analyse mit dive: Das Wichtigste auf einen Blick

1Was zeigt docker history genau an?
Eine Liste aller Layer eines Images mit dem Befehl, der den jeweiligen Layer erzeugt hat, und der Größe, die dieser Layer zur Gesamtgröße beiträgt. Es ist der schnellste erste Schritt, um einen auffällig großen Layer zu identifizieren.
2Warum sollte ich --no-trunc bei docker history verwenden?
Ohne diese Option kürzt Docker den angezeigten Befehl auf eine feste Länge, was bei komplexen, verketteten RUN-Befehlen wichtige Details abschneidet. --no-trunc zeigt den vollständigen Befehl, was die Fehlersuche deutlich erleichtert.
3Was kann dive, was docker history nicht kann?
dive zeigt die einzelnen Dateien innerhalb jedes Layers mit ihrer jeweiligen Größe, nicht nur die Gesamtgröße des Layers. Zusätzlich berechnet es eine Effizienz-Kennzahl, die sichtbar macht, wie viel Speicherplatz durch überschriebene oder gelöschte Dateien verschwendet wird.
4Warum bleiben gelöschte Dateien trotzdem Teil der Image-Größe?
Docker-Layer sind unveränderlich. Eine Löschung in einem späteren Layer erzeugt nur einen Whiteout-Marker, der die Datei im finalen Dateisystem versteckt, physisch aber weiterhin im ursprünglichen Layer vorhanden bleibt und damit die Image-Größe beeinflusst.
5Wie installiere ich dive?
Auf Debian/Ubuntu über das offizielle .deb-Paket vom GitHub-Release, auf macOS über Homebrew mit brew install dive, und für andere Linux-Distributionen steht ein statisch gelinktes Binary ohne weitere Abhängigkeiten zur Verfügung.
6Was bedeutet die Effizienz-Kennzahl in dive?
Sie setzt die Summe der tatsächlich genutzten, einzigartigen Dateigröße ins Verhältnis zur Gesamtgröße aller Layer. Ein Wert deutlich unter 90 Prozent deutet auf erheblichen verschwendeten Speicherplatz durch überschriebene oder gelöschte Dateien hin.
7Kann dive automatisiert in einer CI-Pipeline laufen?
Ja, über die Umgebungsvariable CI=true aktiviert dive einen nicht-interaktiven Modus, der das Image gegen in einer .dive-ci-Datei definierte Schwellenwerte prüft und mit einem Fehler-Exit-Code endet, wenn diese unterschritten werden.
8Was ist die häufigste Ursache für aufgeblähte Images?
Nicht bereinigte Paketmanager-Caches, etwa der apt-Paketindex nach apt-get install, wenn die Bereinigung nicht im selben RUN-Befehl erfolgt. Dasselbe Muster tritt bei npm- und pip-Caches sowie bei temporären Build-Artefakten auf.
9Warum sollten Installation und Cache-Bereinigung im selben RUN-Befehl stehen?
Weil jeder RUN-Befehl einen eigenen, unveränderlichen Layer erzeugt. Nur wenn Installation und Bereinigung im selben Befehl über && verkettet werden, geht ausschließlich die Netto-Größendifferenz in den finalen Layer ein.
10Wie helfen Multi-Stage-Builds bei der Image-Größe?
Build-Werkzeuge und Zwischenartefakte werden in einer separaten Build-Stage installiert, die im finalen Image gar nicht erst auftaucht. Nur die tatsächlich benötigten Laufzeit-Artefakte werden in den letzten, schlanken Stage kopiert.