Von Code Sentinel, Technical Project Manager bei Java Fleet Systems Consulting

Was bisher geschah
- Teil 1: Docker-Basics: Container vs. VMs, Images, Container, Volumes; Postgres lokal als Container.
- Teil 2: Service Orchestration mit Docker Compose. Wir haben eine Spring Boot Personen-API gebaut, Postgres + pgAdmin orchestriert, DataFaker für Startdaten genutzt und die Controller-Logik in eine Service-Schicht ausgelagert.
Jetzt geht’s in die „echte Welt“ Production Docker: kleine, sichere Images, sichere Defaults, Healthchecks, Logs, Metriken und Deployment-Checks.
Kurze Zusammenfassung – Das Wichtigste in 30 Sekunden
- Multi-stage builds reduzieren Image-Größe und Angriffsfläche.
- Security-Basics: non-root, least-privilege, read-only FS, secrets nicht ins Image backen.
- Monitoring/Operations: Healthchecks (App + Compose), Logs, Metriken (Actuator/Prometheus).
- Deployment-Readiness: Ressourcen-Limits, Neustart-Policies, SBOM/Scan, Version-Pinning & Immutability.
Moin! Code Sentinel hier – Zeit für den Realitäts-Check 🛡️
„Läuft bei mir“ ist nicht „Produktionsbereit“. Production Docker heißt: klein, sicher, beobachtbar. Alles andere ist Glückspiel. Lass uns das sauber bauen.
1) Multi‑Stage Build für die Personen‑API (Spring Boot)
Ziel: Build-Tool raus, nur Runtime drin. Kleines, schnelles, sicheres Image.
app/Dockerfile (Multi‑Stage)
# --- Stage 1: Build (mit Maven) ---
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
COPY pom.xml .
RUN mvn -q -B -e -DskipTests dependency:go-offline
COPY src ./src
RUN mvn -q -B -DskipTests package
# --- Stage 2: Runtime (schlank) ---
FROM eclipse-temurin:21-jre
# Alternative für minimalistischere Images:
# FROM gcr.io/distroless/java21-debian12
ENV APP_HOME=/opt/app
WORKDIR ${APP_HOME}
# Non-root User anlegen (least privilege)
RUN useradd --system --uid 10001 --create-home appuser
USER 10001:10001
# Nur das fette JAR übernehmen
COPY --from=build /workspace/target/persons-compose-demo-0.0.1-SNAPSHOT.jar app.jar
# Immutable defaults
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]
Warum so?
- Stage 1 enthält Maven & Cache (bleibt im Build).
- Stage 2 ist nur das JRE + App → kleineres Attack Surface.
- USER 10001: kein root im Container.
Tipp: Mit Spring Boot Layered JARs (spring-boot:repackage -DskipTests) werden Libs/Classes getrennt gecacht → schnellere Rebuilds.
2) Security Best Practices (kompakt & wirksam)
- Non-root ausführen (
USER 10001). - Read-only root FS & tmpfs nur falls nötig.
- Capabilities droppen; in Dev meist nicht nötig.
- Secrets nie im Image backen: via Env, Docker Secrets, Vault.
- Minimal Base Image (JRE / distroless).
- Versions pinnen (Image-Tags, Dependencies).
- SBOM/Scan vor Deploy (z. B. Syft/Trivy/CycloneDX).
Compose-Härtung (Ausschnitt)
services:
app:
image: ghcr.io/dein-org/persons-app:1.0.0
read_only: true
tmpfs:
- /tmp
user: "10001:10001"
cap_drop: ["ALL"]
security_opt:
- no-new-privileges:true
environment:
SPRING_PROFILES_ACTIVE: prod
# Secrets NIE committen – Beispiel nur für Demo!
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/devdb
SPRING_DATASOURCE_USERNAME: devuser
SPRING_DATASOURCE_PASSWORD: devpass
Für echte Secrets: Docker Swarm/K8s Secrets oder Vault/1Password; Env‑Vars gelten sonst als Configuration, nicht als Geheimnis.
3) Healthchecks, Restart‑Policies & Ressourcen‑Limits
App‑Health (Spring Boot)
application.yml (Prod‑Profil):
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
probes:
enabled: true
Spring Boot liefert /actuator/health (+ liveness/readiness, wenn aktiviert).
Compose‑Healthcheck
services:
app:
healthcheck:
test: ["CMD", "wget", "-qO", "-", "http://localhost:8080/actuator/health"]
interval: 10s
timeout: 3s
retries: 10
restart: unless-stopped
deploy:
resources:
limits:
cpus: "1.0"
memory: "768M"
reservations:
cpus: "0.25"
memory: "256M"
Restart‑Policy schützt vor Crash-Loops. Limits/Reservations sorgen für Planbarkeit und faire Ressourcennutzung.
4) Logs & Metriken – was in Produktion zählt
Logs: Standard‑Output, strukturiert (JSON oder pattern). In Compose/K8s übernimmt die Plattform das Sammeln.
Spring Boot Konfiguration (Beispiel Pattern statt Logback‑XML):
# application-prod.properties
logging.pattern.level=%5p
logging.pattern.console=%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} [%thread] %-5level %logger{36} - %msg%n
Metriken:
- Aktiviere Spring Boot Actuator + Prometheus‑Exporter (Micrometer).
- Sammle CPU, Heap, GC, HTTP‑Latenzen.
- Visualisiere mit Grafana (optional ein Compose‑Profil „observability“ hinzufügen).
Beispiel Compose‑Erweiterung (optional):
services:
prometheus:
image: prom/prometheus:latest
volumes: [./ops/prometheus.yml:/etc/prometheus/prometheus.yml:ro]
ports: ["9090:9090"]
grafana:
image: grafana/grafana-oss:latest
ports: ["3000:3000"]
5) Image‑Sicherheit & Lieferkette
- SBOM erzeugen (z. B. mit Syft): Artefaktliste fürs Image.
- Image scannen (z. B. mit Trivy): bekannte CVEs erkennen.
- Signieren/Verifizieren (z. B. cosign): Supply‑Chain absichern.
- Tagging‑Strategie: immutable tags (
1.0.0, Git‑SHA), keinlatestin Prod.
Quick‑Checks (lokal):
# SBOM syft packages ghcr.io/dein-org/persons-app:1.0.0 -o cyclonedx-json > sbom.json # Vulnerability Scan trivy image --exit-code 1 --severity HIGH,CRITICAL ghcr.io/dein-org/persons-app:1.0.0 # Sign (Beispiel) cosign sign ghcr.io/dein-org/persons-app:1.0.0 cosign verify ghcr.io/dein-org/persons-app:1.0.0
6) Deployment‑Varianten
- Compose (Prod‑Light): Kleine Teams, Ein‑/Zwei‑Host‑Deployments.
- Swarm: Secrets/Configs/Rolling Updates (einfacher als K8s).
- Kubernetes: Standard ab Teamgröße N; Health/Scaling/Ingress nativ – aber auch mehr Komplexität.
- GitOps: Manifeste versionieren, Deployments über Pull‑Requests.
Empfehlung: Starte mit Compose Prod‑Light, automatisiere Builds/Scans, nutze immutable Tags, beobachte Metriken. Wächst das System → K8s evaluieren.
7) Beispiel: Prod‑Overrides für unsere Personen‑API
docker-compose.prod.yml (Auszug)
services:
app:
image: ghcr.io/dein-org/persons-app:1.0.0
environment:
SPRING_PROFILES_ACTIVE: prod
read_only: true
tmpfs: ["/tmp"]
user: "10001:10001"
cap_drop: ["ALL"]
security_opt: ["no-new-privileges:true"]
healthcheck:
test: ["CMD", "wget", "-qO", "-", "http://localhost:8080/actuator/health"]
interval: 10s
timeout: 3s
retries: 10
deploy:
resources:
limits: { cpus: "1.0", memory: "768M" }
reservations: { cpus: "0.25", memory: "256M" }
Run:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
FAQ – Häufige Fragen
F1: Distroless oder JRE‑Image?
Distroless ist kleiner/sicherer (kein Paketmanager/Shell), Debugging aber schwieriger. Für Prod prima, für Dev oft JRE‑Image.
F2: Reicht ein Healthcheck auf /actuator/health?
Für Start/Readiness ja. Für tiefere Checks (DB, externe Services) eigene Health‑Contributor nutzen.
F3: Wie gehe ich mit Secrets um?
Nicht im Image, nicht im Repo. Nutze Docker/K8s Secrets oder Vault; lokal nur Platzhalter.
F4: Warum kein latest?
Nicht reproduzierbar. Verwende immutable Tags (Semver, Commit‑SHA).
F5: Was ist der schnellste Security‑Boost?
Non‑root, read‑only FS, cap_drop=ALL, kleines Base‑Image, regelmäßiger Trivy‑Scan.
Teaser & Projekt‑Verweis
Damit du loslegen kannst: Teil 2 Projekt (Persons‑API + Compose) ist bereit.
Für Prod‑Tests: Ersetze das Dockerfile im Projekt durch die Multi‑Stage‑Variante und ergänze die Compose‑Overrides.
📦 Projekt (Teil 2): persons-compose-demo.zip
👉 Download hier
Nächste Schritte (Code Sentinel‑Checkliste):
- [ ] Multi‑Stage Dockerfile integrieren
- [ ] Non‑root + read‑only + cap_drop aktivieren
- [ ] Healthchecks + Limits setzen
- [ ] SBOM/Trivy in CI einbauen
- [ ] Immutable Tags + Registry‑Policies
Tags: #Docker #Security #MultiStageBuild #Monitoring #SpringBoot #DevOps #SBOM

