1. Multi-Service Setup: Web + DB + Redis + Nginx
Ein typisches Web-Projekt besteht aus mehreren Services: der eigentlichen Applikation, einer Datenbank, einem Cache und einem Reverse Proxy. Alles manuell zu orchestrieren kostet Zeit — Claude Code generiert das vollständige docker-compose.yml auf Basis einer einfachen Beschreibung.
Prompt: Vollständiges Multi-Service Setup
bashclaude "Erstelle ein docker-compose.yml für eine Node.js-API mit:
- Web-Service: Node.js 20 auf Port 3000, Dockerfile im Verzeichnis
- PostgreSQL 16 mit persistentem Volume
- Redis 7 als Session-Cache
- Nginx als Reverse Proxy auf Port 80/443
- Health Checks für alle Services
- Saubere Netzwerk-Trennung (frontend, backend)
Kommentiere jeden Abschnitt auf Deutsch."
Claude Code erzeugt ein vollständiges docker-compose.yml mit allem was dazu gehört:
docker-compose.ymlversion: '3.9'
services:
# ─── Reverse Proxy ─────────────────────────────────
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx_logs:/var/log/nginx
depends_on:
web:
condition: service_healthy
networks:
- frontend
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
# ─── Node.js Web-App ───────────────────────────────
web:
build:
context: .
dockerfile: Dockerfile
target: production
environment:
NODE_ENV: production
DATABASE_URL: postgresql://app:${DB_PASSWORD}@postgres:5432/appdb
REDIS_URL: redis://redis:6379
PORT: "3000"
expose:
- "3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- frontend
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 20s
timeout: 5s
retries: 5
start_period: 30s
# ─── PostgreSQL Datenbank ──────────────────────────
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d:ro
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 10s
timeout: 5s
retries: 5
# ─── Redis Cache ────────────────────────────────────
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
# ─── Volumes ────────────────────────────────────────
volumes:
postgres_data:
driver: local
redis_data:
driver: local
nginx_logs:
driver: local
# ─── Networks ───────────────────────────────────────
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # Kein Internet-Zugriff für DB/Redis
internal: true Flag beim Backend-Netzwerk ist entscheidend: PostgreSQL und Redis haben damit keinen direkten Internetzugang — ein einfacher aber wirkungsvoller Sicherheitsgewinn.
2. Networks und Service Discovery
Docker Compose erstellt standardmäßig ein gemeinsames Netzwerk für alle Services. Für produktionsnahe Setups solltest du benutzerdefinierte Netzwerke verwenden — für klare Segmentierung und sichere Service-Isolation.
Inter-Service DNS: Wie Services sich finden
Jeder Service ist im Netzwerk automatisch über seinen Servicenamen erreichbar. web erreicht postgres einfach über den Hostnamen postgres — Docker's eingebautes DNS löst das auf.
bashclaude "Erkläre mir die Netzwerk-Architektur meines docker-compose.yml.
Welche Services können sich gegenseitig erreichen?
Wo gibt es Isolation? Zeige mir ein Netzwerkdiagramm als ASCII-Art."
Internet
│
▼
┌─────────────┐ frontend-net
│ nginx │ ──────────────── Port 80/443
└──────┬──────┘
│ frontend-net
▼
┌─────────────┐ frontend-net + backend-net
│ web │ ───────────────────────────── Port 3000 (expose only)
└──────┬──────┘
│ backend-net (internal)
┌────┴────┐
▼ ▼
postgres redis
(5432) (6379)
↑ Kein Internet-Zugriff (internal: true)
Externe Netzwerke einbinden
Manchmal teilen sich mehrere Compose-Projekte ein Netzwerk — etwa wenn ein separater Monitoring-Stack auf die gleichen Services zugreifen soll. Claude Code generiert die notwendige Konfiguration:
bashclaude "Ich habe ein separates docker-compose.yml für mein Monitoring (Prometheus + Grafana).
Wie verbinde ich das mit meinem App-Stack über ein externes Netzwerk?
Zeige mir beide Compose-Files mit den nötigen Anpassungen."
docker-compose.yml (App-Stack)networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
monitoring:
external: true # Muss vorher erstellt werden
name: monitoring_net # Exakter Name des externen Netzes
docker-compose.monitoring.ymlservices:
prometheus:
image: prom/prometheus:latest
networks:
- monitoring_net
networks:
monitoring_net:
driver: bridge
# Netzwerk erstellen: docker network create monitoring_net
Aliases und Custom-DNS
docker-compose.ymlservices:
postgres:
networks:
backend:
aliases:
- db # Erreichbar als "postgres" UND "db"
- database # Drei Namen für denselben Service
3. Volumes und persistente Daten
Volumes sind das Gedächtnis deiner Container. Ohne sie verliert PostgreSQL bei jedem Neustart alle Daten. Claude Code kennt alle Volume-Typen und wählt den richtigen je nach Use Case.
| Typ | Syntax | Wann nutzen |
|---|---|---|
| Named Volume | postgres_data:/var/lib/pgsql |
Datenbanken, persistente App-Daten, Redis-Snapshots |
| Bind Mount | ./src:/app/src:ro |
Source-Code in Dev, Config-Files, SSL-Zertifikate |
| tmpfs | type: tmpfs, target: /tmp |
Test-Caches, Session-Daten, temporäre Uploads |
Named Volumes mit Backup-Labels
docker-compose.ymlvolumes:
postgres_data:
driver: local
labels:
com.myapp.backup: "true"
com.myapp.backup.schedule: "daily"
com.myapp.description: "PostgreSQL Hauptdatenbank"
uploads:
driver: local
driver_opts:
type: none
o: bind
device: /data/uploads # Host-Pfad für User-Uploads
Bind Mounts für Entwicklung
docker-compose.override.yml (Dev)services:
web:
volumes:
- ./src:/app/src # Live-Reload im Dev
- ./package.json:/app/package.json:ro
- node_modules:/app/node_modules # Anonymes Volume verhindert Host-Override
volumes:
node_modules: # Leere Definition = Docker verwaltet es
tmpfs für Tests
docker-compose.test.ymlservices:
web:
tmpfs:
- /tmp
- /run
postgres:
tmpfs:
- /var/lib/postgresql/data # Tests auf RAM-Disk = 10x schneller
tmpfs laufen ausschließlich im RAM. Das macht Integration-Tests deutlich schneller, da kein Disk-I/O stattfindet. Nach dem Container-Stop sind die Daten weg — perfekt für isolierte Tests.
4. Health Checks und Dependencies
Das klassische Problem: Der Web-Container startet, aber PostgreSQL ist noch nicht bereit. Mit depends_on allein ist das nicht lösbar — es wartet nur auf den Container-Start, nicht auf Service-Readiness. Die Lösung ist condition: service_healthy kombiniert mit echten healthcheck-Definitionen.
Das Dependency-Problem und seine Lösung
bashclaude "Mein Node.js-Container startet bevor PostgreSQL ready ist und crasht.
Wie konfiguriere ich depends_on mit service_healthy korrekt?
Welche healthcheck-Befehle eignen sich für postgres, redis, elasticsearch?"
docker-compose.ymlservices:
web:
depends_on:
postgres:
condition: service_healthy # Wartet bis healthcheck grün
redis:
condition: service_healthy
migrations:
condition: service_completed_successfully # Einmalige Jobs
# DB-Migrations als eigener Service
migrations:
build: .
command: npm run migrate
depends_on:
postgres:
condition: service_healthy
restart: no # Nur einmal laufen
networks:
- backend
Health Checks für alle gängigen Services
docker-compose.yml# PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s # Erste Checks erst nach 30s
# Redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 3
# Node.js API
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 20s
timeout: 5s
retries: 5
start_period: 30s
# Elasticsearch
healthcheck:
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q '\"status\":\"green\"'"]
interval: 30s
timeout: 10s
retries: 10
start_period: 60s # ES braucht länger zum Starten
start_period ist entscheidend für Services die länger zum Hochfahren brauchen (Elasticsearch, MongoDB). In dieser Zeit zählen fehlgeschlagene Checks NICHT als Fehler — der Container gilt nicht als unhealthy.
5. Override-Files für Dev und Prod
Das eleganteste Muster bei Docker Compose ist das Schichten von Konfigurationsdateien. Eine Basis-docker-compose.yml enthält alles was immer gilt. Darüber legen sich environment-spezifische Overrides.
Das Drei-Datei-Muster
docker-compose.yml ← Basis (immer geladen)
docker-compose.override.yml ← Dev-Overrides (automatisch geladen)
docker-compose.prod.yml ← Prod-Overrides (explizit laden)
docker-compose.test.yml ← Test-Overrides (explizit laden)
bash# Development (automatisch: base + override)
docker compose up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Tests
docker compose -f docker-compose.yml -f docker-compose.test.yml run --rm web npm test
docker-compose.override.yml (Development)
docker-compose.override.ymlservices:
web:
build:
target: development # Dev-Stage im Dockerfile
command: npm run dev # Hot-Reload statt Node
volumes:
- ./src:/app/src # Source-Code live mounten
- ./.env:/app/.env:ro
environment:
NODE_ENV: development
DEBUG: "app:*"
ports:
- "3000:3000" # Direkt erreichbar (nicht nur via Nginx)
- "9229:9229" # Node.js Debugger-Port
postgres:
ports:
- "5432:5432" # Direkt mit pgAdmin erreichbar
redis:
ports:
- "6379:6379" # Direkt mit RedisInsight erreichbar
# Nur in Dev: pgAdmin UI
pgadmin:
image: dpage/pgadmin4:latest
environment:
PGADMIN_DEFAULT_EMAIL: admin@local.dev
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "8080:80"
networks:
- backend
docker-compose.prod.yml (Production)
docker-compose.prod.ymlservices:
web:
build:
target: production
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
postgres:
deploy:
resources:
limits:
memory: 1G
secrets:
- db_password # Docker Secret statt .env
secrets:
db_password:
external: true # docker secret create db_password ./password.txt
6. Secrets und Environment-Management
Environment-Variablen direkt im Compose-File sind ein Sicherheitsrisiko — sie landen im Git-Repository. Claude Code kennt die richtigen Muster für sichere Credential-Verwaltung.
Die Hierarchie: Von schlecht zu sicher
| Methode | Sicherheit | Empfehlung |
|---|---|---|
| Hardcoded im YAML | Kritisch | Niemals verwenden |
environment: VAR=value |
Schlecht | Nur für nicht-sensitive Werte |
${VAR} aus Shell-Umgebung |
Mittel | OK für lokale Dev-Umgebungen |
env_file: .env |
Mittel | OK mit .gitignore, nicht ideal für Prod |
| Docker Secrets | Gut | Empfohlen für Production |
| External Secret Store (Vault) | Optimal | Für komplexe Prod-Setups |
Prompt: Secrets-Setup generieren
bashclaude "Mein docker-compose.yml hat Passwörter direkt als environment-Variablen.
Migriere das auf Docker Secrets für Production.
Zeige mir: 1) Secret erstellen, 2) Compose-Anpassung, 3) Wie die App das Secret liest."
Docker Secrets: Vollständige Implementierung
docker-compose.prod.ymlservices:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_DB: appdb
# Secret wird als Datei gemountet, Postgres liest _FILE-Variante
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
web:
environment:
# App liest Secret selbst aus /run/secrets/
DB_PASSWORD_FILE: /run/secrets/db_password
API_KEY_FILE: /run/secrets/api_key
secrets:
- db_password
- api_key
secrets:
db_password:
external: true
api_key:
external: true
secrets-setup.sh#!/bin/bash
# Secrets einmalig erstellen (nur bei erstem Deployment)
echo "your-secure-db-password" | docker secret create db_password -
echo "your-api-key" | docker secret create api_key -
# Secrets auflisten
docker secret ls
# Secrets werden als Dateien gemountet unter:
# /run/secrets/db_password
# /run/secrets/api_key
src/config.jsconst readSecret = (name) => {
const filePath = process.env[`${name}_FILE`];
if (filePath) {
return require('fs').readFileSync(filePath, 'utf8').trim();
}
return process.env[name]; // Fallback für lokale Dev
};
module.exports = {
dbPassword: readSecret('DB_PASSWORD'),
apiKey: readSecret('API_KEY'),
};
.env-Files sicher nutzen
docker-compose.ymlservices:
web:
env_file:
- .env # Basis .env (committed, ohne Secrets)
- .env.local # Lokale Overrides (gitignored)
- .env.${ENV:-dev} # Umgebungs-spezifisch (.env.prod gitignored)
.env im Git-Repository darf nur Struktur (Variablennamen ohne Werte oder mit Dummy-Werten) enthalten. Echte Secrets kommen aus .env.local, .env.prod (beide in .gitignore) oder Docker Secrets.
7. Production: Watchtower, Rolling Updates, Backup-Strategie
Production-Deployments mit Docker Compose unterscheiden sich erheblich von lokalen Dev-Setups. Claude Code kennt die richtigen Muster für automatische Updates, Zero-Downtime-Deploys und Datensicherung.
Watchtower: Automatische Image-Updates
bashclaude "Ich möchte Watchtower für automatische Container-Updates nutzen.
Zeige mir: Basis-Setup, Notification via Telegram, Scheduling,
und wie ich bestimmte Container vom Auto-Update ausschließe."
docker-compose.prod.yml # ─── Watchtower: Automatische Updates ──────────────
watchtower:
image: containrrr/watchtower:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
WATCHTOWER_CLEANUP: "true" # Alte Images löschen
WATCHTOWER_INCLUDE_RESTARTING: "true"
WATCHTOWER_SCHEDULE: "0 0 4 * * *" # Täglich 04:00 Uhr
WATCHTOWER_NOTIFICATIONS: slack
WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL: ${SLACK_WEBHOOK}
WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER: watchtower-prod
labels:
# Watchtower sich selbst vom Auto-Update ausschließen
com.centurylinklabs.watchtower.enable: "false"
restart: unless-stopped
docker-compose.yml# Service vom Watchtower ausschließen:
services:
postgres:
labels:
# Datenbank-Container NIEMALS automatisch updaten!
com.centurylinklabs.watchtower.enable: "false"
Rolling Updates ohne Downtime
bashclaude "Wie mache ich Rolling Updates mit Docker Compose ohne Downtime?
Mein Setup: 3 Web-Replicas hinter Nginx. Zeige mir das komplette Deployment-Script."
deploy.sh#!/bin/bash
set -euo pipefail
IMAGE_TAG=${1:-latest}
echo "Deploying version: $IMAGE_TAG"
# 1. Neues Image pullen
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull web
# 2. Scale auf doppelte Kapazität (alte + neue Instanzen gleichzeitig)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --scale web=6 --no-recreate
# 3. Warten bis neue Container healthy
sleep 30
# 4. Alte Container stoppen (Nginx verteilt nur noch auf neue)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --scale web=3
# 5. Cleanup alte Images
docker image prune -f
echo "Deployment abgeschlossen!"
Backup-Strategie für PostgreSQL-Volumes
backup.sh#!/bin/bash
# Tägliches PostgreSQL-Backup mit Retention
BACKUP_DIR="/backup/postgres"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
mkdir -p "$BACKUP_DIR"
# Backup via pg_dump im laufenden Container
docker compose exec -T postgres pg_dumpall \
-U app \
--clean \
--if-exists \
| gzip > "$BACKUP_DIR/backup_$TIMESTAMP.sql.gz"
echo "Backup erstellt: backup_$TIMESTAMP.sql.gz"
echo "Größe: $(du -sh $BACKUP_DIR/backup_$TIMESTAMP.sql.gz | cut -f1)"
# Alte Backups löschen
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
echo "Backups älter als $RETENTION_DAYS Tage gelöscht."
# Optionaler Upload zu S3/B2
# rclone copy "$BACKUP_DIR/backup_$TIMESTAMP.sql.gz" remote:myapp-backups/
restore.sh#!/bin/bash
BACKUP_FILE=${1:?"Usage: $0 "}
echo "WARNUNG: Alle Daten werden überschrieben!"
read -p "Fortfahren? (y/N): " confirm
[[ "$confirm" != "y" ]] && exit 1
# Restore via pipe
gunzip -c "$BACKUP_FILE" | docker compose exec -T postgres psql -U app appdb
echo "Restore abgeschlossen."
Backup als Docker Compose Service
docker-compose.prod.yml # ─── Backup-Service ─────────────────────────────────
backup:
image: postgres:16-alpine
volumes:
- /backup/postgres:/backup
- ./scripts/backup.sh:/backup.sh:ro
environment:
PGPASSWORD: ${DB_PASSWORD}
PGHOST: postgres
PGUSER: app
depends_on:
postgres:
condition: service_healthy
command: sh -c "crond -f -d 8" # Alpine cron daemon
networks:
- backend
restart: unless-stopped
claude "Review meinen docker-compose.prod.yml auf Production-Readiness: Security, Resource-Limits, Restart-Policies, Logging, Backup, Health Checks." — das deckt häufige Lücken auf.
Quick Reference: Häufige Compose-Befehle mit Claude
| Aufgabe | Claude-Prompt | Ergebnis |
|---|---|---|
| Services starten | claude "docker compose up Flags für detached + force-recreate?" |
Optimale CLI-Flags je Situation |
| Log-Analyse | docker compose logs -f web 2>&1 | claude --print "Was ist das Problem?" |
Root Cause + Fix-Vorschlag |
| Ressource-Limits | claude "Setze sinnvolle CPU/Memory-Limits für meine Services" |
deploy.resources Konfiguration |
| Netzwerk debuggen | claude "Warum erreicht web den postgres Service nicht?" |
Netzwerk-Diagnose + Fix |
| Secrets rotieren | claude "Anleitung: Docker Secret ohne Downtime rotieren" |
Step-by-step Rotation Script |
| Compose validieren | docker compose config | claude --print "Fehler oder Verbesserungen?" |
YAML-Audit + Optimierungen |
Docker Compose mit KI-Unterstützung meistern
Starte jetzt mit Claude Code und generiere Production-Ready Compose-Setups in Minuten statt Stunden.
Kostenlos ausprobieren →