erkennen, verstehen und gezielt beheben
Viele Dockerfiles wachsen organisch und akkumulieren dabei Muster, die Images aufblähen, die Build-Zeit verdoppeln oder die Sicherheit des Containers untergraben. Ein strukturierter Dockerfile-Review deckt diese Anti-Patterns auf – bevor sie in der Produktion zum Problem werden.
Inhaltsverzeichnis
- 1. Warum Dockerfile-Reviews unverzichtbar sind
- 2. Layer Bloat: zu viele RUN-Befehle und unnötige Daten
- 3. Cache-Invalidierung durch falsche Reihenfolge
- 4. Root-User: das häufigste Sicherheits-Anti-Pattern
- 5. Unkontrollierter Build-Context ohne .dockerignore
- 6. Fehlende Multi-Stage-Builds bei kompilierten Sprachen
- 7. Secrets und Credentials im Image – ein fatales Anti-Pattern
- 8. Fehlende Healthchecks und falsche ENTRYPOINT-Konfiguration
- 9. Anti-Patterns im direkten Vergleich
- 10. Zusammenfassung und Review-Checkliste
- 11. FAQ
1. Warum Dockerfile-Reviews unverzichtbar sind
Ein Dockerfile ist die Blaupause jedes Container-Images – und gleichzeitig eine der am häufigsten vernachlässigten Codedateien in einem Projekt. Während Anwendungscode durch Tests, Code-Reviews und statische Analyse abgesichert wird, landen Dockerfile Anti-Patterns oft unbemerkt in der Produktion. Das Ergebnis: Images, die mehrere Gigabyte groß sind, Build-Zeiten von zehn Minuten, Container, die als Root laufen, und Credentials, die dauerhaft in Image-Layern eingefroren sind.
Ein strukturierter Dockerfile-Review ist keine akademische Übung, sondern hat direkte Auswirkungen auf Build-Geschwindigkeit, Deployment-Häufigkeit und Angriffsfläche. In der Praxis zeigt sich immer wieder dasselbe Muster: Der ursprüngliche Entwickler hat das Dockerfile schnell zusammengestellt, es hat funktioniert und niemand hat es seitdem angefasst. Dabei haben sich zehn bis fünfzehn Dockerfile Anti-Patterns eingeschlichen, die sich beheben ließen, wenn man weiß, wonach man sucht.
Die folgenden Abschnitte gehen durch die häufigsten Dockerfile Anti-Patterns – von Layer Bloat über Cache-Invalidierung bis zu Secrets im Image – und zeigen jeweils den direkten Fix. Jeder Abschnitt schließt mit einer konkreten Regel, die sich in eine Team-Checkliste für Dockerfile-Reviews übernehmen lässt.
2. Layer Bloat: zu viele RUN-Befehle und unnötige Daten
Das häufigste Dockerfile Anti-Pattern ist die Zerstreuung von Paketinstallationen über mehrere RUN-Befehle. Jeder RUN-Befehl erzeugt einen neuen Layer im Image. Wenn ein Layer Pakete installiert und ein späterer Layer den Cache dieser Pakete löscht, verringert das zwar den Platz im obersten Layer, aber nicht die tatsächliche Image-Größe – denn die gelöschten Dateien existieren weiterhin im vorherigen Layer. Dieses Muster findet sich in vielen offiziellen und inoffiziellen Images und ist für Image-Größen von über einem Gigabyte mitverantwortlich.
Das korrekte Gegenmuster kombiniert alle verwandten Befehle in einem einzigen RUN-Befehl und löscht den Paket-Cache in derselben Anweisung. apt-get install gefolgt von && rm -rf /var/lib/apt/lists/* im gleichen RUN-Block reduziert die Layer-Größe direkt. Gleiches gilt für apk add --no-cache bei Alpine-basierten Images. Ein weiteres Dockerfile Anti-Pattern im Bereich Layer Bloat ist das Kopieren des gesamten Source-Codes vor dem Installationsschritt – das invalidiert den Cache bei jeder Code-Änderung, obwohl sich die Abhängigkeiten nicht geändert haben.
# ANTI-PATTERN: separate RUN layers — cache of deleted files stays in layer 1
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y curl wget git
RUN rm -rf /var/lib/apt/lists/* # too late — previous layer retains the cache
# CORRECT: combine into a single RUN, delete cache in the same layer
FROM ubuntu:22.04
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
wget \
git \
&& rm -rf /var/lib/apt/lists/*
# ANTI-PATTERN: copy all sources before installing dependencies
COPY . /app
RUN pip install -r /app/requirements.txt # cache busted on every code change
# CORRECT: copy only the dependency manifest first
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt
COPY . /app # source copy here — does NOT bust dependency cache
3. Cache-Invalidierung durch falsche Reihenfolge
Der Docker Layer Cache ist eines der mächtigsten Werkzeuge für schnelle Builds – und wird durch die Reihenfolge der Anweisungen im Dockerfile gesteuert. Das zentrale Anti-Pattern: häufig ändernde Daten früh in das Dockerfile zu platzieren. Sobald eine Anweisung den Cache invalidiert, werden alle nachfolgenden Layer neu gebaut – auch wenn sie sich nicht geändert haben. Ein COPY . . am Anfang des Dockerfiles bedeutet, dass jede Code-Änderung alle nachfolgenden Layer – inklusive der oft minutenlangen Abhängigkeitsinstallation – neu auslöst.
Die Lösung ist das Prinzip "vom Seltensten zum Häufigsten": Systempakete, dann Laufzeit-Konfiguration, dann Abhängigkeitsdateien (package.json, composer.json, requirements.txt), dann Abhängigkeiten installieren, zuletzt den eigentlichen Source-Code kopieren. In der Praxis reduziert diese Umsortierung die durchschnittliche Build-Zeit bei Code-Änderungen von Minuten auf Sekunden, weil alle teuren Schritte gecacht bleiben. Das Dockerfile Anti-Pattern der falschen Reihenfolge ist rein logischer Natur – der Build ist korrekt, aber unnötig langsam.
# ANTI-PATTERN: wrong order invalidates cache on every code change
FROM node:22-alpine
WORKDIR /app
COPY . . # any file change busts ALL subsequent layers
RUN npm ci # expensive — runs on every commit, even doc changes
RUN npm run build
# CORRECT: dependency manifest first, source last
FROM node:22-alpine
WORKDIR /app
# Step 1: only copy manifests (changes rarely)
COPY package.json package-lock.json ./
# Step 2: install dependencies (cached unless manifests change)
RUN npm ci --omit=dev
# Step 3: copy source (changes often, but only triggers fast steps below)
COPY . .
RUN npm run build
# CORRECT PHP example — same principle with Composer
FROM php:8.4-fpm-alpine
WORKDIR /var/www/html
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --optimize-autoloader
COPY . .
RUN composer run-script post-install-cmd
4. Root-User: das häufigste Sicherheits-Anti-Pattern
Container, die als Root laufen, sind eines der am häufigsten diskutierten und gleichzeitig am häufigsten ignorierten Dockerfile Anti-Patterns. Wenn ein Prozess im Container als Root läuft und ein Angreifer eine Schwachstelle in der Anwendung ausnutzt, erhält er Root-Rechte innerhalb des Containers – und bei schwacher Kernel-Isolation potenziell auch auf dem Host. Kubernetes-Cluster mit aktivierter Pod Security Admission lehnen Container mit Root-User in Restricted-Profilen standardmäßig ab. Viele Cloud-Provider und Compliance-Frameworks fordern Non-Root-Container explizit.
Das Fix-Muster ist einfach: einen dedizierten Benutzer im Dockerfile anlegen und vor dem letzten ENTRYPOINT oder CMD mit USER darauf wechseln. Bei Alpine-basierten Images: addgroup -S appgroup && adduser -S appuser -G appgroup. Bei Debian/Ubuntu: groupadd -r appgroup && useradd -r -g appgroup appuser. Kritisch ist dabei, dass die Anwendungsverzeichnisse korrekte Eigentümer bekommen, bevor auf den Non-Root-User gewechselt wird. Ein weiteres subtiles Anti-Pattern: der User wird zwar angelegt, aber Volumes und gemountete Verzeichnisse gehören weiterhin Root, was zur Laufzeit zu Berechtigungsfehlern führt.
5. Unkontrollierter Build-Context ohne .dockerignore
Beim Ausführen von docker build sendet der Docker-Client den gesamten Build-Context an den Docker-Daemon – standardmäßig das aktuelle Verzeichnis und alle darin enthaltenen Dateien. Ohne eine .dockerignore-Datei bedeutet das: Node-Module mit hunderten Megabyte, Git-Repository-Geschichte, lokale Secrets-Dateien, IDE-Konfigurationen und Testdaten landen vollständig im Build-Context und verlangsamen den Build erheblich. Das ist ein klassisches Dockerfile Anti-Pattern, das allein durch eine korrekte .dockerignore-Datei behoben wird.
Die .dockerignore-Datei folgt denselben Musterregeln wie .gitignore. Mindestinhalt für die meisten Projekte: node_modules, .git, *.log, .env*, dist, vendor (PHP/Composer), __pycache__. Bei Projekten mit mehreren Gigabyte an Abhängigkeiten kann das Hinzufügen einer .dockerignore die Zeit für das Senden des Build-Contexts von dreißig Sekunden auf unter eine Sekunde reduzieren. Ein verwandtes Dockerfile Anti-Pattern: der Einsatz von COPY . . ohne vorherige .dockerignore, der dazu führt, dass Secrets und Credentials in das finale Image gelangen.
# .dockerignore — always place in project root alongside Dockerfile
# Prevents secrets, large deps, and VCS history from entering build context
# Version control
.git
.gitignore
# Environment and secrets — critical security item
.env
.env.*
*.pem
*.key
secrets/
# Dependency directories (reinstalled during build)
node_modules/
vendor/
__pycache__/
*.pyc
# Build artifacts and caches
dist/
build/
.cache/
var/cache/
pub/static/
# IDE and OS
.idea/
.vscode/
*.DS_Store
Thumbs.db
# Test and documentation files
tests/
*.test.js
*.spec.ts
README.md
docs/
# Logs
*.log
logs/
6. Fehlende Multi-Stage-Builds bei kompilierten Sprachen
Für Go, Rust, Java, TypeScript und PHP (mit Composer) ist der Multi-Stage-Build kein optionales Feature, sondern eine Grundvoraussetzung für produktionsreife Images. Das Dockerfile Anti-Pattern ohne Multi-Stage-Build: der vollständige Build-Stack – Compiler, SDK, Build-Tools, Dev-Abhängigkeiten – landet im finalen Image, obwohl zur Laufzeit nur das kompilierte Artefakt benötigt wird. Ein Go-Binary, das in einem Image mit dem vollständigen Go-Compiler läuft, ist im besten Fall unnötig groß und im schlechtesten Fall ein Sicherheitsrisiko, weil der Compiler selbst als Angriffswerkzeug genutzt werden kann.
Multi-Stage-Builds lösen dieses Dockerfile Anti-Pattern strukturell: eine builder-Stage enthält alle Build-Tools und kompiliert das Artefakt, eine finale runtime-Stage basiert auf einem minimalen Basis-Image und kopiert nur das fertige Artefakt. Das Ergebnis ist ein Image, das nur enthält, was zur Laufzeit tatsächlich benötigt wird. Für Go-Applikationen kann das Image dabei von 800 MB auf unter 20 MB schrumpfen – nur durch den korrekten Einsatz von Multi-Stage-Builds ohne Änderung am Anwendungscode.
7. Secrets und Credentials im Image – ein fatales Anti-Pattern
Credentials, API-Schlüssel oder SSH-Keys, die über COPY oder ENV in ein Dockerfile gelangen, bleiben dauerhaft in der Image-History eingefroren – auch wenn sie in einem späteren Layer scheinbar gelöscht werden. docker history image-name und docker save machen alle Layer und ihre Inhalte sichtbar. Das ist ein kritisches Dockerfile Anti-Pattern, das in öffentlichen Registries zu unmittelbaren Credential-Lecks führt und in privaten Registries die Angriffsfläche bei einer Kompromittierung erhöht.
Secrets gehören nicht in Dockerfiles – niemals. Die korrekten Alternativen: Build-Time-Secrets über RUN --mount=type=secret (Docker BuildKit), der das Secret nur während des Build-Befehls als temporäres Dateisystem einhängt und es nicht im finalen Image speichert. Zur Laufzeit: Secrets über Umgebungsvariablen aus einer gesicherten Quelle (Vault, Kubernetes Secrets, AWS Secrets Manager) injizieren. Docker Compose ermöglicht das mit dem secrets-Schlüssel. Das Dockerfile Anti-Pattern der eingebackenen Secrets ist unter den zehn häufigsten Sicherheitsproblemen in Container-Umgebungen – und eins der am einfachsten vermeidbaren.
# ANTI-PATTERN: secret baked into image layer — visible in docker history
FROM php:8.4-fpm-alpine
ENV DATABASE_PASSWORD=supersecret123 # permanent in image history
RUN composer install --no-dev
# Even if you later do: RUN unset DATABASE_PASSWORD
# the ENV layer still contains the value in docker history
# CORRECT: BuildKit secret mount — not stored in any layer
# syntax=docker/dockerfile:1
FROM php:8.4-fpm-alpine
COPY composer.json composer.lock ./
# Secret is mounted as /run/secrets/composer_auth only during this RUN
RUN --mount=type=secret,id=composer_auth,dst=/root/.composer/auth.json \
composer install --no-dev --no-scripts --optimize-autoloader
# CORRECT: runtime injection via Docker Compose secrets
# docker-compose.yml excerpt:
# services:
# app:
# secrets:
# - db_password
# secrets:
# db_password:
# file: ./secrets/db_password.txt
# Inside container: /run/secrets/db_password — never in image
# Verify no secrets in image history
docker history --no-trunc your-image:tag | grep -i "password\|secret\|key\|token"
8. Fehlende Healthchecks und falsche ENTRYPOINT-Konfiguration
Container ohne HEALTHCHECK-Direktive werden von Orchestratoren wie Kubernetes und Docker Swarm als "running" behandelt, sobald der Prozess gestartet ist – unabhängig davon, ob die Anwendung tatsächlich bereit ist, Anfragen zu verarbeiten. Dieses Dockerfile Anti-Pattern führt zu Race-Conditions beim Start: ein Load Balancer leitet Anfragen an einen Container weiter, der noch nicht bereit ist, und Nutzer sehen Fehler. Ein korrekt konfigurierter HEALTHCHECK definiert einen Befehl, der den tatsächlichen Zustand der Anwendung prüft, und gibt dem Orchestrator die Information, den Container erst dann als verfügbar zu melden, wenn er wirklich bereit ist.
Ein weiteres Dockerfile Anti-Pattern in diesem Bereich: die Verwendung von Shell-Form für ENTRYPOINT statt der Exec-Form. ENTRYPOINT ["sh", "-c", "myapp"] oder ENTRYPOINT myapp wrappen den Prozess in eine Shell, die POSIX-Signale wie SIGTERM nicht korrekt weiterleitet. Wenn Docker oder Kubernetes einen Container stoppen, erhält die Shell das Signal – aber die eigentliche Anwendung nicht, was zu Timeouts und erzwungenen SIGKILL-Abbrüchen führt. Die Exec-Form ENTRYPOINT ["/app/myapp"] macht den Prozess zum direkten PID 1 und empfängt Signale korrekt.
9. Anti-Patterns im direkten Vergleich
Die häufigsten Dockerfile Anti-Patterns lassen sich kategorisieren: manche betreffen die Performance (Build-Zeit, Image-Größe), andere die Sicherheit (Root-User, Secrets), und einige beides gleichzeitig. Die folgende Tabelle gibt einen strukturierten Überblick der häufigsten Anti-Patterns, ihrer Auswirkung und des direkten Fixes.
| Anti-Pattern | Kategorie | Auswirkung | Fix |
|---|---|---|---|
| Separate RUN-Layer | Performance | Große Images, Bloat bleibt in Layern | RUN-Befehle kombinieren, Cache löschen |
| COPY . . zu früh | Performance | Cache-Invalidierung bei jeder Änderung | Abhängigkeiten vor Source-Code kopieren |
| Root-User | Sicherheit | Privilegienausweitung bei Exploits | USER-Anweisung mit Non-Root-Account |
| Kein .dockerignore | Performance + Sicherheit | Secrets im Image, langsamer Build | .dockerignore mit node_modules, .env, .git |
| Secrets in ENV/COPY | Sicherheit | Dauerhaft in Image-History eingefroren | BuildKit --mount=type=secret |
| Kein HEALTHCHECK | Verfügbarkeit | Frühzeitig als verfügbar gemeldete Container | HEALTHCHECK mit curl oder wget |
Ein vollständiger Dockerfile Review adressiert alle Kategorien gleichzeitig. In der Praxis empfiehlt sich das Tool hadolint (Dockerfile Linter), das viele dieser Dockerfile Anti-Patterns statisch erkennt und per CI-Integration automatisch bei jedem Commit prüft. hadolint Dockerfile gibt nummerierte Warnungen mit direkten Hinweisen zurück. Für Security-Scans von fertigen Images ergänzen Tools wie trivy oder grype den Review um Schwachstellendatenbanken.
Mironsoft
Dockerfile-Optimierung, Container-Security und DevOps-Beratung
Dockerfile Anti-Patterns in eurem Projekt eliminieren?
Wir analysieren bestehende Dockerfiles und Container-Setups, identifizieren kritische Anti-Patterns und setzen Multi-Stage-Builds, korrekte Layer-Reihenfolge und sichere Secret-Verwaltung um.
Dockerfile Review
Analyse mit hadolint, trivy und manueller Anti-Pattern-Prüfung
Image-Optimierung
Multi-Stage-Builds, Layer-Konsolidierung und Build-Context-Kontrolle
CI-Integration
hadolint und trivy in GitLab CI / GitHub Actions einbinden
10. Zusammenfassung und Review-Checkliste
Dockerfile Anti-Patterns entstehen selten durch Nachlässigkeit, sondern durch fehlendes Wissen über die Konsequenzen bestimmter Muster. Die wichtigsten Punkte im Überblick: RUN-Befehle zusammenfassen und Cache im gleichen Layer löschen. Abhängigkeits-Manifeste vor dem Source-Code kopieren. Konsequent Non-Root-User einsetzen. Eine vollständige .dockerignore-Datei pflegen. Multi-Stage-Builds für alle kompilierten Sprachen und Build-Artefakte. Secrets ausschließlich über BuildKit-Secrets oder Runtime-Injection verwalten. HEALTHCHECK und Exec-Form für ENTRYPOINT verwenden.
Die Integration von hadolint als Linting-Schritt in die CI-Pipeline stellt sicher, dass neue Dockerfile Anti-Patterns automatisch erkannt werden, bevor ein Image gebaut und deployed wird. Das ergänzt den manuellen Review und schafft eine Baseline, die für das gesamte Team gilt. Ein einmaliger Review-Sprint, der alle bestehenden Dockerfiles durch diese Checkliste führt, reduziert Image-Größen, verkürzt Build-Zeiten und schließt die häufigsten Sicherheitslücken.
Dockerfile Anti-Patterns — Review-Checkliste auf einen Blick
Performance
RUN-Befehle kombinieren, Cache im selben Layer löschen. COPY-Reihenfolge: Manifeste vor Source. Multi-Stage-Build für Compiler und Build-Tools.
Sicherheit
Non-Root-USER. Keine Secrets in ENV oder COPY. BuildKit --mount=type=secret. .dockerignore mit .env, .git, node_modules.
Verfügbarkeit
HEALTHCHECK mit realistischen start-period und interval-Werten. ENTRYPOINT in Exec-Form für korrekte Signalweiterleitung.
Automatisierung
hadolint in CI-Pipeline integrieren. trivy für Image-Schwachstellenscans. docker history --no-trunc auf Secrets prüfen.