Docker Best Practices: 16 Tipps für produktionsreife Container
dockerintermediate

Docker Best Practices: 16 Tipps für produktionsreife Container

Vollständiger Leitfaden zu Docker Best Practices zur Optimierung Ihrer Dockerfiles, Absicherung Ihrer Container und Reduzierung der Image-Größe. Praktische Beispiele und Produktionstipps.

Antoine C
9 min read
#docker#best-practices#dockerfile#security#performance#devops

Docker Best Practices: 16 Tipps für produktionsreife Container

Die meisten Dockerfiles, die ich reviewe, teilen dieselben Probleme: 2 GB große Images, wenn 200 MB ausreichen würden, 15-minütige Builds, die 30 Sekunden dauern könnten, Container, die ohne triftigen Grund als root laufen.

Der Container funktioniert lokal, die Tests bestehen, aber sobald er in Produktion geht, häufen sich die Probleme: überlange Deployment-Zeiten, Sicherheitslücken, explosiver Speicherverbrauch.

Der Unterschied zwischen einem Dockerfile, das "funktioniert", und einem produktionsreifen Dockerfile liegt oft in einem Dutzend Best Practices. Diese Praktiken sind nicht kompliziert, aber sie erfordern ein Verständnis davon, wie Docker unter der Haube funktioniert: Layer-System, Build-Cache, Prozessisolierung.

In diesem Artikel werden Sie die 16 wesentlichen Docker Best Practices entdecken, um von Amateur-Images zu professionellen Containern zu gelangen. Wir behandeln Dockerfile-Optimierung, Sicherheit, Performance und Produktionsmuster.

Dieser Artikel stellt jede Praxis prägnant vor. Um tiefer einzutauchen und in echten Umgebungen zu üben, bietet Train With Docker "Docker Best Practices"-Szenarien, die Sie Schritt für Schritt durch die Implementierung jedes Konzepts führen.

Dockerfile-Optimierung#

1. Verwenden Sie Multi-Stage Builds#

Multi-Stage Builds sind wahrscheinlich die wirkungsvollste Technik zur Reduzierung Ihrer Image-Groesse. Die Idee: Trennen Sie die Build-Umgebung von der Laufzeitumgebung.

dockerfile
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Produktion
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

Die erste Stage enthält alles, was zum Kompilieren benötigt wird (TypeScript, devDependencies, Quellcode). Die zweite Stage behält nur das absolute Minimum: kompilierten Code und Produktionsabhängigkeiten.

Typische Einsparung

Ein Node.js TypeScript-Projekt reduziert sich oft von 1,2 GB auf 150 MB mit einem gut konfigurierten Multi-Stage Build.

2. Ordnen Sie Ihre Anweisungen zur Maximierung des Caches#

Docker cached jede Layer. Wenn sich eine Anweisung ändert, werden alle nachfolgenden Anweisungen neu gebaut. Die Reihenfolge Ihrer Anweisungen beeinflusst direkt Ihre Build-Geschwindigkeit.

dockerfile
# Schlecht: Cache wird bei jeder Code-Änderung ungültig
COPY . .
RUN npm ci
RUN npm run build

# Gut: Abhängigkeiten werden separat gecached
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

Die Regel: Platzieren Sie Elemente, die sich selten ändern, zuerst (Systemabhängigkeiten, dann Pakete, dann Quellcode).

3. Minimieren Sie die Anzahl der Layer#

Jede RUN-, COPY- und ADD-Anweisung erstellt eine neue Layer. Kombinieren Sie verwandte Befehle, um die Anzahl der Layer zu reduzieren.

dockerfile
# Schlecht: 3 Layer
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# Gut: 1 Layer
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
Bereit, es Selbst Auszuprobieren?
Üben Sie diese Docker-Konzepte in einer echten Umgebung mit praktischen Szenarien.

4. Verwenden Sie eine .dockerignore-Datei#

Die .dockerignore-Datei funktioniert wie .gitignore: Sie schließt Dateien vom Build-Kontext aus. Ohne sie kopiert Docker alles, einschließlich node_modules, .git und Ihre Testdateien.

text
node_modules
.git
.gitignore
*.md
.env*
coverage
.nyc_output
dist
Achtung

Ein großer Build-Kontext verlangsamt jeden Build, selbst wenn Sie diese Dateien nicht explizit kopieren. Docker muss sie trotzdem an den Daemon senden.

5. Wählen Sie das richtige Basis-Image#

Die Wahl des Basis-Images beeinflusst die endgültige Größe, Sicherheit und verfügbare Abhängigkeiten.

ImageGrößeAnwendungsfall
node:20~400 MBNiemals in Produktion
node:20-slim~75 MBWenn Sie Systempakete benötigen
node:20-alpine~50 MBStandardwahl
gcr.io/distroless/nodejs20~188 MBMaximale Sicherheit

Alpine verwendet musl libc anstelle von glibc. In 95% der Fälle funktioniert es einwandfrei. Für die restlichen 5% (bestimmte native Binärdateien) verwenden Sie -slim.

Googles Distroless-Images sind schwerer als Alpine, aber sie enthalten keine Shell, keinen Paketmanager und keine Systemtools. Nur die Runtime (hier Node.js) und ihre Abhängigkeiten sind vorhanden. Ergebnis: weniger Angriffsfläche und kein Weg für einen Angreifer, Befehle im Container auszuführen.

Container-Sicherheit#

6. Führen Sie niemals als root aus#

Standardmäßig laufen Docker-Container als root. Dies ist ein großes Sicherheitsproblem: Wenn ein Angreifer Ihre Anwendung kompromittiert, hat er root-Privilegien im Container.

dockerfile
FROM node:20-alpine

# Erstellen Sie einen Nicht-root-Benutzer
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

WORKDIR /app
COPY --chown=nextjs:nodejs . .

# Wechseln Sie den Benutzer vor CMD
USER nextjs

CMD ["node", "index.js"]
Verifizierung

Sie können den aktuellen Benutzer mit docker exec <container> whoami überprüfen. Wenn es root zurückgibt, haben Sie ein Problem.

7. Begrenzen Sie Linux Capabilities#

Standardmäßig gewährt Docker Ihren Containern eine Reihe von Linux Capabilities (Systemberechtigungen). Die meisten sind für eine Standardanwendung unnötig und stellen ein Sicherheitsrisiko dar.

bash
# Entfernen Sie alle Capabilities und fügen Sie nur die notwendigen hinzu
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp

Gängigste Capabilities:

CapabilityVerwendung
NET_BIND_SERVICEAuf einem Port < 1024 lauschen
CHOWNDateibesitzer ändern
SETUID / SETGIDBenutzer/Gruppe wechseln
SYS_PTRACEProzesse debuggen (in Prod vermeiden)
Prinzip der minimalen Rechte

Beginnen Sie immer mit --cap-drop=ALL und fügen Sie dann nur die Capabilities hinzu, die Ihre Anwendung tatsächlich benötigt. Wenn Ihre App abstürzt, werden die Logs Ihnen sagen, welche Capability fehlt.

Für Docker Compose:

yaml
services:
  app:
    image: myapp
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
Setzen Sie Theorie in Praxis Um
Wenden Sie das Gelernte mit interaktiven Docker-Szenarien und echten Umgebungen an.

8. Verwenden Sie minimale Basis-Images#

Je mehr Pakete ein Image enthält, desto größer ist seine Angriffsfläche. Googles Distroless-Images enthalten nur Ihre Anwendung und ihre Runtime-Abhängigkeiten, ohne Shell oder Systemtools.

dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app/dist /app
WORKDIR /app
CMD ["index.js"]

Der Nachteil: Es ist unmöglich, docker exec zum Debuggen zu verwenden. Das ist beabsichtigt. In der Produktion sollten Sie keine Shell in Ihren Containern benötigen.

9. Scannen Sie Ihre Images auf Schwachstellen#

Docker-Images können CVEs (bekannte Schwachstellen) in ihren Systempaketen oder Abhängigkeiten enthalten.

bash
# Mit Docker Scout (in Docker Desktop integriert)
docker scout cves <image>

# Mit Trivy (Open Source)
trivy image <image>

# Mit Snyk
snyk container test <image>

Integrieren Sie diese Scans in Ihre CI/CD. Ein Scan, der eine kritische Schwachstelle entdeckt, sollte das Deployment blockieren.

10. Verwalten Sie Ihre Secrets richtig#

Legen Sie niemals Secrets in Ihr Dockerfile oder Image. Sie bleiben im Layer-Verlauf erhalten.

dockerfile
# NIEMALS: Das Secret bleibt im Image
ENV DATABASE_URL=postgres://user:password@host/db

# BESSER: Verwenden Sie Docker Secrets (Build-Zeit)
# Das Secret wird während des Builds temporär in /run/secrets/ gemountet
# Es wird niemals in eine Image-Layer geschrieben
RUN --mount=type=secret,id=db_url \
    export DATABASE_URL=$(cat /run/secrets/db_url) && \
    npm run migrate

# Zum Bauen mit diesem Secret:
# docker build --secret id=db_url,src=.env .

# IN PRODUKTION: Übergeben Sie Secrets zur Laufzeit
docker run -e DATABASE_URL=$DATABASE_URL myapp

Für Docker Swarm und Kubernetes verwenden Sie deren native Secrets-Mechanismen.

Performance und Image-Größe#

11. Bereinigen Sie Caches in derselben Layer#

Paketmanager hinterlassen Caches. Wenn Sie sie in einer separaten Layer bereinigen, bleibt der Cache in vorherigen Layern erhalten.

dockerfile
# Python
RUN pip install --no-cache-dir -r requirements.txt

# Node.js
RUN npm ci --only=production && npm cache clean --force

# Debian/Ubuntu
RUN apt-get update && \
    apt-get install -y --no-install-recommends package && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
Meistern Sie Docker Praktisch
Gehen Sie über die Theorie hinaus - üben Sie mit echten Containern und Orchestrierungsszenarien.

12. Verwenden Sie COPY anstelle von ADD#

COPY macht genau das, was der Name sagt: Dateien kopieren. ADD macht dasselbe, extrahiert aber automatisch Archive (.tar, .gz) und kann Dateien von einer URL herunterladen. Diese impliziten Verhaltensweisen machen das Dockerfile weniger lesbar und können Sicherheitslücken einführen, wenn eine URL kompromittiert ist oder ein Archiv unerwartete Dateien enthält. Bevorzugen Sie COPY für seine Vorhersehbarkeit.

dockerfile
# Bevorzugen
COPY ./src /app/src

# Vermeiden (es sei denn, Sie müssen ein Archiv extrahieren)
ADD ./src /app/src

Build Best Practices#

13. Fügen Sie Labels für Metadaten hinzu#

Labels helfen bei der Identifizierung und Dokumentation Ihrer Images.

dockerfile
LABEL org.opencontainers.image.title="Meine Anwendung"
LABEL org.opencontainers.image.description="Backend API"
LABEL org.opencontainers.image.version="1.2.3"
LABEL org.opencontainers.image.authors="[email protected]"
LABEL org.opencontainers.image.source="https://github.com/org/repo"

Diese Labels folgen der OCI-Spezifikation und werden von Docker Hub, GitHub Container Registry und anderen Registries erkannt.

14. Implementieren Sie Health Checks#

Ein Health Check ermöglicht es Docker (und Orchestratoren) zu überprüfen, ob Ihre Anwendung tatsächlich funktioniert.

dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

Ohne Health Check betrachtet Docker einen Container als "healthy", sobald er startet, selbst wenn die Anwendung nach 5 Sekunden abstürzt.

15. Verstehen Sie den Unterschied zwischen ENTRYPOINT und CMD#

  • ENTRYPOINT: definiert den Hauptbefehl (selten überschrieben)
  • CMD: definiert Standardargumente (leicht überschreibbar)
dockerfile
# Empfohlenes Muster
ENTRYPOINT ["node"]
CMD ["index.js"]

# Ermöglicht:
docker run myapp                    # Führt node index.js aus
docker run myapp other-script.js    # Führt node other-script.js aus

Verwenden Sie die Exec-Form ["cmd", "arg"] anstelle der Shell-Form cmd arg. Die Exec-Form ermöglicht eine bessere Signal-Behandlung.

Logging und Observability#

16. Schreiben Sie Ihre Logs nach stdout/stderr#

Docker erfasst automatisch alles, was nach stdout und stderr geht. Schreiben Sie nicht in Log-Dateien.

javascript
// Gut: stdout
console.log(JSON.stringify({ level: "info", message: "User logged in", userId: 123 }));

// Schlecht: Datei
fs.appendFileSync("/var/log/app.log", "User logged in\n");

Das JSON-Format strukturiert Ihre Logs für Aggregationstools (ELK, Datadog, CloudWatch).

dockerfile
# Leiten Sie Logs nicht in Dateien um
CMD ["node", "index.js"]

# Nicht so:
CMD ["node", "index.js", ">", "/var/log/app.log"]

Orchestrierung und Produktion#

Sobald Ihre Images optimiert sind, gelten einige zusätzliche Praktiken zur Laufzeit.

Begrenzen Sie Ressourcen, um zu verhindern, dass ein Container den Server monopolisiert:

bash
docker run --memory="512m" --cpus="0.5" myapp

Konfigurieren Sie Restart-Policies für Resilienz:

bash
docker run --restart=unless-stopped myapp

Verwenden Sie Rolling Updates mit Docker Swarm oder Kubernetes für unterbrechungsfreie Deployments.

Docker Swarm

Wenn Sie sich auf die DCA-Zertifizierung vorbereiten oder Docker Swarm vertiefen möchten, bietet Train With Docker praktische Szenarien mit vorkonfigurierten Clustern.

Fazit#

Diese 16 Docker Best Practices decken die wesentlichen Aspekte ab: Build-Optimierung, Sicherheit, Performance und Produktionsbereitschaft. Systematisch angewendet, verwandeln sie Amateur-Dockerfiles in professionelle Konfigurationen.

Das Wichtigste: Verstehen Sie, warum jede Praxis existiert. Multi-Stage Builds reduzieren die Größe, weil sie Build und Runtime trennen. Der Cache funktioniert pro Layer, daher ist die Reihenfolge wichtig. Nicht-root-Container begrenzen die Auswirkungen einer Kompromittierung.

Dieser Artikel streift jede Praxis, ohne in Implementierungsdetails einzutauchen. Um selbst Hand anzulegen, bietet Train With Docker "Docker Best Practices"-Szenarien, mit denen Sie jedes Konzept in vorkonfigurierten Umgebungen üben können, ohne lokal etwas installieren zu müssen.

Docker Best Practices: 16 Tipps für produktionsreife Container