Dockerfile-Optimierung in der Praxis: 5 Tipps, die Image-Größe um 80 % reduzieren

Der Fortschrittsbalken im Terminal hängt seit 30 Minuten bei „Pushing to registry”.
3,2 GB.
Das erste Docker-Image für eine Node.js-App – Dockerfile Schritt für Schritt nach Online-Tutorial. Build erfolgreich, aber die Größe war unerwartet. Am nächsten Morgen fragt ein Kollege auf Slack: „Hast du da etwa das ganze Betriebssystem mitgepackt? Mein Laptop-Laufwerk ist fast voll.”
Ubuntu-Basis-Image? node_modules? Oder Build-Tools? Jedenfalls: Ein einfacher API-Service, dessen Image 50-mal so groß ist wie der gesamte Projektcode. Nach Durchsicht der Docker-Dokumentation und Best Practices habe ich dieses 3,2-GB-Monster auf 180 MB reduziert. Minus 94 %.
In diesem Artikel geht es um die fünf wirksamsten Tipps aus diesem Prozess. Nicht nur das Wie, sondern auch das Warum – denn Prinzipien verstehen ist wichtiger als Befehle auswendig zu lernen.
Warum Docker-Images so groß werden
Bevor wir zu den Optimierungstipps kommen, sollten wir die Ursache verstehen.
Docker-Images sind geschichtet. Jede RUN-, COPY- oder ADD-Anweisung erzeugt eine neue Dateisystem-Layer. Diese Layer stapeln sich und bilden schließlich das Image, das Sie sehen. Entscheidend: Jede Layer fügt nur hinzu, löscht aber nichts.
Beispiel. In Ihrem Dockerfile steht:
RUN apt-get update
RUN apt-get install -y build-essential
RUN rm -rf /var/lib/apt/lists/*
Oberflächlich betrachtet löscht die letzte Zeile den apt-Cache. Tatsächlich ist der Cache aber dauerhaft in der zweiten Layer gespeichert. Die dritte Layer markiert nur „diese Dateien wurden gelöscht” – die Daten bleiben im Image und belegen Speicher.
Wie beim Umzug: Sie fotografieren jedes Aufräumen, werfen am Ende den Müll weg – aber die Fotos mit dem Müll müssen Sie trotzdem mitnehmen. Etwas absurd, aber so funktioniert Copy-on-Write bei Docker.
Mit docker history auf einem unoptimierten Image sehen Sie, dass viele Layer zusammen deutlich größer sind als die tatsächlich benötigten Dateien.
Ein oft übersehener Punkt: Die Wahl des Basis-Images legt die Untergrenze fest. ubuntu:20.04 allein hat 72 MB, node:16 sogar 1,09 GB – basierend auf einem vollständigen Debian-System mit Tools, die Sie vielleicht nie brauchen.
Damit ist die Optimierungsstrategie klar: Weniger Layer, leichtere Basis-Images, Installation und Bereinigung in derselben Layer.
Tipp 1: Das richtige Basis-Image – Vorteil von Anfang an
Die Wahl des Basis-Images ist wie die Lage beim Immobilienkauf. Liegt die Basis falsch, hat späteres Feintuning Grenzen.
Ein Größenvergleich:
node:16→ 1,09 GBnode:16-slim→ 240 MBnode:16-alpine→ 174 MBalpine:latest→ 5,6 MB
Der Unterschied ist offensichtlich. Beim Wechsel von node:16 auf node:16-alpine sank mein Image von 1,2 GB auf 400 MB – ohne eine Zeile Code zu ändern.
Was ist Alpine Linux?
Eine Linux-Distribution speziell für Container. Minimalistisch, nur die Kernkomponenten. musl libc statt glibc, Paketmanager apk statt apt.
Die Vorteile:
- Geringes Volumen (5 MB vs. 72 MB bei Ubuntu)
- Sicherheit (minimale Angriffsfläche)
- Schneller Start
Es gibt aber Fallstricke.
Alpine-Kompatibilitätsprobleme
Wegen musl libc laufen manche vorkompilierte Binaries nicht. Einmal hing ein Node.js-Nativmodul in C++ und meldete auf Alpine „library not found”. Nach stundenlangem Debuggen war die Ursache libc.
Pragmatische Empfehlung:
- Zuerst Alpine-Variante testen (Suffix
-alpine) - Bei Kompatibilitätsproblemen auf
-slimwechseln (Debian-basiert, aber deutlich schlanker) - Erst im Notfall Standard-Image – selten nötig
Die Anpassung ist trivial:
# Vorher
FROM node:16
# Nachher
FROM node:16-alpine
Eine Zeile – 800 MB gespart.
Wirkung prüfen
Nach dem Build:
docker images your-image-name
Spalte SIZE prüfen. Bleibt das Image groß, liegt das Problem nicht nur am Basis-Image – weiter unten gibt es mehr Tipps.
Tipp 2: RUN-Anweisungen zusammenführen, Layer reduzieren
Dieser Tipp ist leicht zu verstehen, wird in der Praxis aber oft übersehen.
Wie erwähnt: Jede RUN-Anweisung erzeugt eine Layer. Entscheidend: Löschen wirkt nur innerhalb derselben Layer.
Negativbeispiel:
# Falsch (3 Layer)
RUN apt-get update
RUN apt-get install -y python3 gcc
RUN rm -rf /var/lib/apt/lists/*
So landet der apt-Cache (oft dutzende MB) in Layer 2. Layer 3 markiert nur „Dateien weg” – die Daten bleiben im Image.
Richtig mit && verbinden:
# Richtig (1 Layer)
RUN apt-get update && \
apt-get install -y python3 gcc && \
rm -rf /var/lib/apt/lists/*
Installation und Bereinigung in einer Layer – das Löschen ist echt.
Die Rolle des Backslash
Das \ erlaubt Zeilenumbrüche für Lesbarkeit. Ohne ihn wird die Zeile unübersichtlich lang.
Wann zusammenführen, wann nicht
Nicht jede RUN-Anweisung sollte zusammengeführt werden. Faustregel:
- Zusammenführen: Installation + Bereinigung, Download + Entpacken + Archiv löschen
- Getrennt lassen: logisch unabhängige Schritte, häufig geänderte Schritte (Build-Cache)
Beispiel:
# Sinnvolle Schichtung
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN npm install
RUN npm run build
Systemabhängigkeiten in einer Layer, npm install separat (package.json ändert sich oft), Build separat. Bei package.json-Änderung bleibt die System-Layer gecacht.
Praxis: Ein Projekt mit 12 RUN-Anweisungen, nach Optimierung 4 – Image von 520 MB auf 320 MB.
Tipp 3: Multi-Stage-Build – nur das Nötige mitnehmen
Multi-Stage-Build ist das wirksamste Werkzeug zum Image-Schlanken. Ohne Wenn und Aber.
Kernidee: Build und Laufzeit trennen.
Ein Go-Programm zu kompilieren braucht die gesamte Go-Toolchain (hunderte MB), das Binary vielleicht nur 10 MB. Die Toolchain im finalen Image mitzupacken ist reine Verschwendung.
Multi-Stage-Build löst das: Mehrere Phasen in einem Dockerfile – Phase 1 baut, Phase 2 übernimmt nur die Artefakte.
Node.js-Beispiel:
# === Build-Phase ===
FROM node:16-alpine AS builder
WORKDIR /app
# Abhängigkeitsdateien kopieren
COPY package*.json ./
RUN npm install
# Quellcode kopieren und bauen
COPY . .
RUN npm run build
# === Laufzeit-Phase ===
FROM node:16-alpine
WORKDIR /app
# Nur benötigte Dateien kopieren
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Entscheidend: COPY --from=builder. Dateien aus Phase 1 (builder) in Phase 2 – das finale Image enthält nur Phase 2, alle Zwischenprodukte von Phase 1 entfallen.
Wann Multi-Stage-Build sinnvoll ist
Typische Szenarien:
- Kompilierte Sprachen: Go, Rust, C++ mit Compiler
- Frontend-Projekte: TypeScript-Kompilierung, Webpack-Bundling
- Build-Tools nötig: z. B. Python-Projekte mit gcc für native Extensions
Mein Node.js-Projekt: TypeScript zu JavaScript. Quellcode 400 MB (inkl. @types in node_modules), dist nur 2 MB. Mit Multi-Stage-Build von 400 MB auf 220 MB.
Ein häufiger Fehler
Manche führen in der Laufzeit-Phase erneut npm install aus – „Abhängigkeiten braucht man ja”. Dann landen auch devDependencies im Image.
Richtig: In der Build-Phase npm install (inkl. Dev-Abhängigkeiten zum Kompilieren), dann node_modules kopieren. Oder präziser:
# Build-Phase
RUN npm install
# Laufzeit-Phase
RUN npm install --production
Nur Produktionsabhängigkeiten – noch 30–40 % weniger Volumen.
Multi-Stage-Build wirkt anfangs komplex, ist aber elegant: Wie beim Packen – zu Hause (Build) alles ausbreiten, ins Flugzeug (Laufzeit) nur das Nötige im Koffer.
Tipp 4: Mit .dockerignore unnötige Dateien ausschließen
.dockerignore funktioniert wie .gitignore, wird aber oft vernachlässigt.
Bei COPY . . sendet Docker das gesamte Verzeichnis als Build-Context an den Daemon. Hunderte MB node_modules, .git-Historie, Tests, Logs – alles wird übertragen und kopiert.
Auch wenn diese Dateien später nicht im Image landen, verlangsamt der Prozess den Build. Zudem können sensible Dateien mit hineinrutschen (z. B. Schlüssel in .env).
Lösung: .dockerignore im Projektroot anlegen.
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
README.md
.vscode
.idea
*.md
.DS_Store
coverage/
.pytest_cache/
__pycache__/
*.pyc
dist-local/
Grundprinzipien
Diese Einträge gehören hinein:
- Bestehende Abhängigkeitsverzeichnisse: node_modules, vendor, target (werden beim Build neu installiert)
- IDE-Konfiguration: .vscode, .idea, .editorconfig
- Git: .git, .gitignore (.git oft dutzende MB)
- Dokumentation: README, CHANGELOG, docs/
- Sensible Daten: .env, credentials.json, *.pem
Einmal vergaß ich .git – jeder Build übertrug 500 MB Git-Historie. Mit .dockerignore: Build von 2 Minuten auf 30 Sekunden, Image ebenfalls kleiner.
Praktischer Tipp
Unklar, was kopiert wird? Einmal bauen, Container betreten:
docker run --rm -it your-image sh
ls -lah
Unerwünschte Dateien? In .dockerignore eintragen.
Einfach, aber wirkungsvoll – besonders bei Frontend-Projekten, wo dist, node_modules und .cache schnell GB erreichen.
Tipp 5: Paketmanager-Cache bereinigen
Paketmanager (npm, pip, apt, apk) hinterlassen nach der Installation Cache. Lokal beschleunigt das Folge-Installationen – im Docker-Image ist es totale Ballast.
Viele wissen, dass bereinigt werden soll – tun es aber falsch.
Bereinigung muss in derselben RUN-Anweisung stehen
Noch einmal: Bereinigung und Installation in derselben Layer.
# ❌ Wirkungslose Bereinigung
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/* # vergeblich
# ✅ Wirksame Bereinigung
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
Übersicht nach Paketmanager:
Node.js (npm/yarn)
# npm – klassisch
RUN npm install && \
npm cache clean --force
# npm – einfacher (Cache deaktiviert)
RUN npm install --no-cache
# yarn
RUN yarn install && \
yarn cache clean
Python (pip)
# Direkt: ohne Cache installieren
RUN pip install --no-cache-dir -r requirements.txt
# Oder nach Installation bereinigen
RUN pip install -r requirements.txt && \
rm -rf ~/.cache/pip
Alpine (apk)
# apk mit praktischer Option
RUN apk add --no-cache package-name
# Oder manuell
RUN apk add package-name && \
rm -rf /var/cache/apk/*
Debian/Ubuntu (apt)
RUN apt-get update && \
apt-get install -y package-name && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Messdaten
Python-Projekt, gleiche Abhängigkeiten:
- Ohne Cache-Bereinigung: 450 MB
- Mit Bereinigung: 320 MB
- Mit
--no-cache-dir: 310 MB (am saubersten)
140 MB Unterschied – oft nur ein Parameter.
Entwicklung vs. Produktion
Produktions-Images sollten --production oder --no-dev nutzen. Dev-Abhängigkeiten machen oft 30–50 % des Volumens aus.
# Node.js – nur Produktionsabhängigkeiten
RUN npm install --production
# Python – nur benötigte Pakete (in requirements.txt trennen)
RUN pip install --no-cache-dir -r requirements-prod.txt
Kombiniert wirken diese fünf Tipps multiplikativ, nicht additiv. Mein 3,2-GB-Projekt landete bei 180 MB – alle Tipps zusammen.
Vollständiger Fall: Node.js-App Schritt für Schritt optimiert
Genug Theorie – ein echter Express-API-Service und die Dockerfile-Evolution.
Vorher (1,2 GB)
FROM node:16
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
Einfach, aber riesig.
Schritt 1: Alpine-Basis-Image (→ 400 MB, -67 %)
FROM node:16-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
Eine Zeile – 800 MB weniger.
Schritt 2: .dockerignore (→ 380 MB, -5 %)
.dockerignore anlegen:
node_modules
.git
*.md
.env
coverage
Wenig Größenersparnis, aber deutlich schnellerer Build.
Schritt 3: Multi-Stage-Build (→ 220 MB, -42 %)
# Build-Phase
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Laufzeit-Phase
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Größter Effekt – alle Build-Zwischenprodukte weg.
Schritt 4: Nur Produktionsabhängigkeiten + Cache bereinigen (→ 180 MB, -18 %)
# Build-Phase
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Laufzeit-Phase
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production --no-cache && \
npm cache clean --force
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
Finale Version: 180 MB – 85 % weniger als die anfänglichen 1,2 GB.
Optimierungs-Roadmap
1,2 GB (node:16 Original)
↓ Alpine
400 MB (-67 %)
↓ .dockerignore
380 MB (-5 %)
↓ Multi-Stage-Build
220 MB (-42 %)
↓ Produktionsabhängigkeiten + Bereinigung
180 MB (-18 %)
────────────────
Gesamt minus 85 %
Welche Tipps wirken am stärksten?
Aus diesem Fall:
- Alpine-Basis-Image: Sofort spürbar, leicht umzusetzen
- Multi-Stage-Build: Größter Effekt, etwas Lernaufwand
- Bereinigung und Produktionsabhängigkeiten: Feintuning, summiert sich
Bei wenig Zeit: zuerst die beiden ersten.
Fazit
Die fünf Tipps im Überblick:
- Alpine-Basis-Image – von Anfang an kleiner
- RUN-Anweisungen zusammenführen – Installation und Bereinigung in einer Layer
- Multi-Stage-Build – nur Laufzeitdateien mitnehmen
- .dockerignore – unnötige und sensible Dateien ausschließen
- Paketmanager-Cache bereinigen – Parameter wie
--no-cache
Kombiniert wirken sie am besten. Alpine + Multi-Stage-Build lösen etwa 80 % der Größenprobleme, der Rest durch Bereinigung und Ausschluss.
Sofort loslegen
Nicht erst warten, bis es weh tut. Nehmen Sie ein bestehendes Projekt und testen Sie die fünf Tipps:
- Alpine möglich? (meist ja)
- Getrennte Installation und Bereinigung? (zusammenführen)
- Multi-Stage-Build ergänzen (Pflicht bei kompilierten Projekten)
.dockerignoreanlegen--no-cachefür Paketmanager setzen
Nach dem Build docker images – und vergleichen, wie viel Sie sparen.
Weiterführend
Für Vertiefung:
- Docker BuildKit Cache-Mounts
- Distroless-Images (Google, noch kleiner als Alpine)
- Image-Security-Scanning (Trivy, Grype)
Dockerfile-Optimierung ist kein einmaliger Schritt. Bei jedem Build die Image-Größe prüfen – so bleibt das Volumen unter Kontrolle.
Viel Erfolg beim Schlanken Ihrer Images.
Vollständiger Workflow zur Dockerfile-Optimierung
5 Tipps, die Image-Größe um 80 % reduzieren – von 3,2 GB auf 180 MB, minus 94 %
⏱️ Estimated time: 1 hr
- 1
Step 1: Tipp 1: Alpine-Basis-Image verwenden
Vorteile von Alpine-Basis-Images:
• Geringes Volumen (nur 5 MB, Ubuntu hat 200 MB)
• Hohe Sicherheit (minimale Angriffsfläche)
• Einfacher Paketmanager (apk)
• Geeignet für Produktionsumgebungen
Alpine nutzen:
• FROM ubuntu:20.04 durch FROM alpine:latest ersetzen
• apk statt apt-get: apk add --no-cache nodejs npm
• Image-Größe von 200 MB auf 5 MB – minus 95 % - 2
Step 2: Tipp 2–3: RUN-Anweisungen zusammenführen und Multi-Stage-Build
RUN-Anweisungen zusammenführen:
• Mehrere RUN-Anweisungen zu einer zusammenfassen – weniger Image-Layer
• Befehle mit && verbinden, \ für Zeilenumbrüche
• Cache am Ende bereinigen:
RUN apt-get update && \
apt-get install -y nodejs npm && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Multi-Stage-Build:
• Phase 1: Vollständiges Build-Image kompiliert
• Phase 2: Minimales Runtime-Image
• Nur Laufzeitdateien behalten, Build-Tools nicht mitpacken
• Image-Größe deutlich reduziert - 3
Step 3: Tipp 4–5: .dockerignore konfigurieren und Cache bereinigen
.dockerignore konfigurieren:
• node_modules, .git, .env, dist usw. ausschließen
• Build-Context verkleinern, Build beschleunigen
Cache bereinigen:
• --no-cache-Parameter: apk add --no-cache
• apt-Cache: apt-get clean && rm -rf /var/lib/apt/lists/*
• npm-Cache: npm cache clean --force
• Image-Größe reduzieren
FAQ
Was sind die 5 Tipps zur Dockerfile-Optimierung?
1) Alpine-Basis-Image (von Ubuntu 200 MB auf Alpine 5 MB, minus 95 %)
2) RUN-Anweisungen zusammenführen (weniger Layer, kleinere Images)
3) Multi-Stage-Build (nur Laufzeitdateien, keine Build-Tools)
4) .dockerignore konfigurieren (node_modules, .git usw. ausschließen)
5) Cache bereinigen (apt-get clean, npm cache clean usw.)
Optimierungsergebnis:
• Node.js-App-Image von 3,2 GB auf 180 MB (-94 %)
• Von 1,2 GB auf 180 MB (-85 %)
• Deployment-Zeit von 30 Minuten auf wenige Minuten
• Image-Größe deutlich reduziert
Warum ist ein Alpine-Basis-Image besser?
• Geringes Volumen (nur 5 MB, Ubuntu hat 200 MB)
• Hohe Sicherheit (minimale Angriffsfläche)
• Einfacher Paketmanager (apk)
• Geeignet für Produktionsumgebungen
Alpine nutzen:
• FROM ubuntu:20.04 durch FROM alpine:latest ersetzen
• apk statt apt-get: apk add --no-cache nodejs npm
• Image-Größe von 200 MB auf 5 MB – minus 95 %
Wie führt man RUN-Anweisungen zusammen?
• Mehrere RUN-Anweisungen zu einer zusammenfassen – weniger Image-Layer
• Befehle mit && verbinden
• \ für Zeilenumbrüche und Lesbarkeit
• Cache am Ende bereinigen:
RUN apt-get update && \
apt-get install -y nodejs npm && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
So reduzieren Sie Layer-Anzahl, Image-Größe und verbessern die Build-Effizienz.
Was sind Best Practices für Dockerfile-Optimierung?
• Alpine-Basis-Image verwenden
• RUN-Anweisungen zusammenführen
• Multi-Stage-Build nutzen
• .dockerignore konfigurieren
• Cache bereinigen
• Basis-Images regelmäßig aktualisieren
Diese Tipps wirken am besten kombiniert. Meine Erfahrung: Alpine + Multi-Stage-Build lösen 80 % der Größenprobleme, die restlichen 20 % durch Bereinigung und Ausschluss.
Dockerfile-Optimierung ist kein einmaliger Schritt, sondern ein kontinuierlicher Prozess. Bei jedem Build die Image-Größe prüfen – so bleibt das Volumen unter Kontrolle.
9 Min. Lesezeit · Veröffentlicht am: 17. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Docker Praxisleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Dockerfile-Einstieg: Erstes Docker-Image von null (mit Beispielen)
Schritt für Schritt Dockerfile schreiben: FROM, RUN, COPY und weitere Kernanweisungen erklärt, typische Anfängerfehler vermeiden – inklusive Node.js-Praxisbeispiel. Danach können Sie Ihr Projekt containerisieren.
Teil 3 von 38
Nächster
Docker Multi-Stage-Build in der Praxis: Go-/Java-/Rust-Images von GB auf MB schlanken
Multi-Stage-Builds im Detail: Go -98 %, Java -86 %, Rust -99,4 %. Mit vollständigen Dockerfiles und Praxis-Pitfalls aus echten Projekten.
Teil 5 von 38
Ähnliche Beiträge
Docker vs. virtuelle Maschine: Performance-Unterschiede und Szenario-Auswahl in 5 Minuten

Docker vs. virtuelle Maschine: Performance-Unterschiede und Szenario-Auswahl in 5 Minuten
Docker-Installation ohne Fallstricke (2025): Vollständige Lösungen von permission denied bis zum erfolgreichen Start


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen