Das optimale Dockerfile
# 1. Spezifisches Base-Image (kein 'latest')
FROM node:20.11-alpine3.19
# 2. OCI-Labels für Metadaten
LABEL org.opencontainers.image.title="MeineApp" \
org.opencontainers.image.version="1.0.0" \
org.opencontainers.image.authors="[email protected]"
# 3. Sicherheit: Kein root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 4. Arbeitsverzeichnis setzen
WORKDIR /app
# 5. Dependencies ZUERST (für Cache-Optimierung)
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# 6. Quellcode danach
COPY --chown=appuser:appgroup . .
# 7. Benutzer wechseln
USER appuser
# 8. Port dokumentieren
EXPOSE 3000
# 9. Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js || exit 1
# 10. Klarer Entrypoint
CMD ["node", "server.js"]
Die wichtigsten Regeln
1. Spezifische Tags statt latest
# Schlecht – nicht reproduzierbar
FROM node:latest
# Gut – deterministisch
FROM node:20.11.0-alpine3.19
2. Layer-Cache optimal nutzen
# Richtige Reihenfolge: was sich selten ändert kommt zuerst
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# Dependencies vor Source-Code
COPY package.json package-lock.json ./
RUN npm ci
# Source-Code zuletzt (invalidiert Cache am häufigsten)
COPY . .
3. .dockerignore
# .dockerignore
node_modules
.git
.env
*.log
dist
coverage
.DS_Store
Dockerfile*
docker-compose*
README.md
4. RUN-Befehle zusammenfassen
# Schlecht – jedes RUN erstellt einen Layer
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# Gut – ein Layer
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
5. Alpine für kleine Images
FROM node:20-alpine # ~50 MB
# statt
FROM node:20 # ~900 MB
6. Healthcheck
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
Ohne Healthcheck weiß Docker nicht ob Ihr Container wirklich funktioniert (nur ob er läuft).
7. ENTRYPOINT vs CMD
# ENTRYPOINT: feste Basis-Kommando
# CMD: Standard-Argumente (überschreibbar)
ENTRYPOINT ["node"]
CMD ["server.js"]
# docker run myapp → node server.js
# docker run myapp worker.js → node worker.js
8. ARG für Build-Zeit Variablen
ARG NODE_ENV=production
ARG APP_VERSION=1.0.0
ENV NODE_ENV=${NODE_ENV}
LABEL version=${APP_VERSION}
docker build --build-arg NODE_ENV=staging --build-arg APP_VERSION=1.2.3 .
Image-Größe analysieren
# Dive – interaktiver Layer-Inspektor
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest myapp:latest
# Einzel-Layer-Größen
docker history myapp:latest
FAQ
Soll ich COPY oder ADD nutzen?
COPY für einfaches Kopieren. ADD nur wenn Sie tar-Archive entpacken oder URLs laden müssen (verwirrend und fehleranfällig).
Wann nutze ich SHELL statt RUN?SHELL ["/bin/bash", "-o", "pipefail", "-c"] vor RUN wenn Sie Pipes nutzen und Fehler korrekt abfangen wollen.
Fazit
Ein gutes Dockerfile kostet 10 Minuten Mehraufwand und spart Stunden bei Debugging, Deployment-Problemen und Security-Incidents.
Container-Optimierungen für Entwicklungsteams in Heidelberg und der Rhein-Neckar-Region. Anfragen.