Docker Compose mit Claude Code: Multi-Container Apps & Produktion 2026

Docker Compose ist das Schweizer Taschenmesser für lokale Entwicklung und einfache Production-Deployments. Mit Claude Code generierst du vollständige Multi-Container-Setups, richtige Networks, Health Checks und Production-Ready Konfigurationen — ohne YAML-Syntax stundenlang nachzuschlagen.

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:

compose network Vollständiges Multi-Service Setup
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
Tip: Das 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."
network Netzwerk-Architektur Visualisierung
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.

volumes Die drei Volume-Arten im Überblick
TypSyntaxWann 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
Performance-Tip: Test-Datenbanken auf 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
Wichtig: 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

base Dateistruktur
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

MethodeSicherheitEmpfehlung
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)
Goldene Regel: .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

prod Watchtower überwacht und aktualisiert Container automatisch
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
Production Checklist: Bevor du einen Compose-Stack live schaltest, frage Claude Code: 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

AufgabeClaude-PromptErgebnis
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 →