Claude Code Docker Compose: Multi-Container-Apps und Microservices mit KI orchestrieren
Docker Compose ist mächtig — und genau deshalb frustrierend, wenn es nicht funktioniert. Ein falsch gesetztes Einzug-Level in der YAML, ein vergessener depends_on-Eintrag, ein Netzwerk das existiert aber nicht erreichbar ist: die Fehler sind klein, die Folgen groß, und die Fehlermeldungen von Docker selten eindeutig.
Claude Code Docker Compose macht das Schreiben, Verstehen und Debuggen von docker-compose.yml-Dateien fundamental anders. Nicht weil Claude Code "Docker kennt" — sondern weil es gleichzeitig deine bestehende Konfiguration, deinen Anwendungscode, deine Umgebungsvariablen und die Docker-Dokumentation im Kontext hat. Dieser Artikel zeigt, wie das konkret funktioniert: von der ersten docker-compose.yml bis zum production-reifen Multi-Service-Setup.
Claude Code Mastery — Docker Compose, Agents, Hooks auf Deutsch
Nicht nur Docker: der Kurs zeigt, wie du Claude Code wirklich produktiv einsetzt — für Entwicklungsabläufe, autonome Agents und professionelle Workflows. Einmalig bezahlt, kein Abo.
Zum Kurs — Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht1. docker-compose.yml verstehen und schreiben
Das Herzstück jedes Docker-Compose-Setups ist die docker-compose.yml-Datei. Sie definiert, welche Services laufen, wie sie konfiguriert sind und wie sie miteinander kommunizieren. Das Problem: YAML ist whitespace-sensitiv, Docker Compose hat seine eigene Semantik für Networking und Volumes, und die Dokumentation ist umfangreich genug, dass man leicht den Überblick verliert.
Claude Code hilft hier auf zwei Ebenen: Erklären und Generieren. Du kannst eine bestehende docker-compose.yml übergeben und fragen, was sie tut. Oder du beschreibst deine Architektur und lässt Claude Code die Datei von Grund auf schreiben.
claude "Erkläre mir diese docker-compose.yml Schritt für Schritt.
Was macht jeder Abschnitt und welche Abhängigkeiten bestehen?" < docker-compose.yml
Die Antwort ist kein Nachkopieren der Dokumentation — Claude Code liest die konkrete Datei und erklärt, was diese Konfiguration tut: welche Ports nach außen exponiert werden, warum ein bestimmter Service von einem anderen abhängt, ob ein Volume korrekt gemountet ist.
Eine neue docker-compose.yml von Grund auf generieren
Noch kraftvoller: du beschreibst deine Anforderungen in natürlicher Sprache, und Claude Code schreibt eine vollständige, produktionsgerechte Compose-Datei:
claude "Erstelle eine docker-compose.yml für eine Next.js-App mit:
- PostgreSQL als Datenbank
- Redis für Session-Cache
- Nginx als Reverse Proxy
- Alle Services im selben privaten Netzwerk
- Volumes für Datenbankpersistenz
- Environment-Variablen aus einer .env-Datei"
Das Ergebnis ist keine generische Vorlage, sondern eine Datei, die auf deine Anforderungen zugeschnitten ist — mit korrektem Networking, sinnvollen Health Checks und einer Struktur, die auch sechs Monate später noch lesbar ist.
YAML-Fallstricke: Docker Compose ist extrem whitespace-sensitiv. Claude Code schreibt korrekt eingerücktes YAML — aber wenn du Teile aus verschiedenen Quellen kombinierst, kann es zu Einrückungs-Fehlern kommen. Lass Claude Code nach dem Zusammenführen immer die komplette Datei validieren: claude "Prüfe diese docker-compose.yml auf YAML-Fehler und Inkonsistenzen" < docker-compose.yml
Bestehende Konfigurationen erweitern
In der Praxis arbeitest du meistens nicht auf der grünen Wiese. Du hast eine bestehende docker-compose.yml und möchtest einen weiteren Service hinzufügen — ohne das bestehende Setup zu bremsen. Claude Code liest die bestehende Datei und fügt den neuen Service konsistent ein: gleiche Netzwerk-Konfiguration, passende Einrückung, korrekte Abhängigkeiten.
claude "Füge einen Elasticsearch-Service zu dieser docker-compose.yml hinzu.
Er soll im selben Netzwerk sein wie die anderen Services, aber nur
intern erreichbar sein — kein Port nach außen." < docker-compose.yml
2. Multi-Container-App aufsetzen (Web + DB + Cache)
Das klassische Drei-Service-Setup — Web-Anwendung, Datenbank, Cache — ist der häufigste Einstiegspunkt in Docker Compose. In der Theorie einfach: drei Services definieren, miteinander verbinden, fertig. In der Praxis gibt es Dutzende Entscheidungen, die du richtig treffen musst.
Hier ist ein vollständiges Beispiel für ein produktionsnahes Setup, das Claude Code auf Anfrage generiert:
services:
web:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
environment:
- DATABASE_URL=postgresql://app:${DB_PASSWORD}@db:5432/appdb
- REDIS_URL=redis://cache:6379
- NODE_ENV=production
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
networks:
- app-network
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: appdb
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
cache:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
networks:
- app-network
volumes:
postgres-data:
redis-data:
networks:
app-network:
driver: bridge
Was auf den ersten Blick wie eine Standard-Vorlage aussieht, enthält viele bewusste Entscheidungen: restart: unless-stopped statt always (damit du Services manuell stoppen kannst), Health Checks für die Datenbank (damit der Web-Service erst startet, wenn die DB wirklich bereit ist), und Redis mit Persistenz und Memory-Limit (damit es im Fehlerfall nicht unbegrenzt wächst).
Services debuggen wenn der Start fehlschlägt
Häufiges Szenario: docker compose up läuft, aber einer der Services startet nicht richtig. Die Logs sind lang, die Fehler kryptisch. Claude Code liest die Logs direkt:
docker compose logs --no-color 2>&1 | claude "Warum startet der Web-Service nicht?
Was muss ich ändern?"
Oder gezielt für einen Service:
docker compose logs web --no-color 2>&1 | claude "Was ist der Fehler und wie behebe ich ihn?"
Timing-Problem bei depends_on: depends_on stellt nur sicher, dass ein Container gestartet ist — nicht, dass der Service darin bereit ist. Eine Datenbank, die 3 Sekunden zum Hochfahren braucht, erzeugt connection-refused-Fehler im Web-Service, auch wenn depends_on: db gesetzt ist. Lösung: Health Checks kombiniert mit condition: service_healthy — wie im Beispiel oben gezeigt.
Mehrere Umgebungen mit einem Basis-Setup
In der Praxis willst du dasselbe Setup für Entwicklung und Produktion nutzen — aber mit unterschiedlichen Konfigurationen. Claude Code hilft, eine saubere Override-Struktur zu erstellen:
claude "Ich habe diese docker-compose.yml für Produktion. Erstelle eine
docker-compose.override.yml für die Entwicklung mit:
- Volume-Mount für Live-Reload
- Debug-Port exponiert
- Weniger restriktive Ressourcengrenzen
- pgAdmin als zusätzlicher Service" < docker-compose.yml
3. Netzwerke und Volumes konfigurieren
Netzwerke und Volumes sind die zwei Konzepte in Docker Compose, die am häufigsten falsch verstanden werden. Netzwerke steuern, welche Services miteinander sprechen können. Volumes steuern, welche Daten über Container-Neustarts hinaus erhalten bleiben. Beide klingen einfach — aber die Details entscheiden darüber, ob dein Setup sicher, performant und wartbar ist.
Netzwerke: Isolation als Standard
Wenn du keine expliziten Netzwerke definierst, erstellt Docker Compose automatisch ein Standard-Netzwerk und verbindet alle Services damit. Das funktioniert — aber es ist nicht ideal. Besser ist es, Netzwerke explizit zu definieren und Services nur dann zu verbinden, wenn sie es wirklich brauchen:
services:
web:
networks:
- frontend
- backend
api:
networks:
- backend
- database
db:
networks:
- database
nginx:
ports:
- "80:80"
- "443:443"
networks:
- frontend
networks:
frontend:
driver: bridge
backend:
driver: bridge
database:
driver: bridge
internal: true # kein Zugang von außen
Mit internal: true auf dem Datenbank-Netzwerk ist die Datenbank nur von Services im selben Netzwerk erreichbar — kein direkter Zugriff von außen, auch wenn der Container-Port per Fehler exponiert würde. Das ist Defense-in-Depth auf Netzwerkebene.
claude "Analysiere dieses Netzwerk-Setup und sage mir: Welcher Service kann mit
welchem anderen kommunizieren? Gibt es Sicherheitsprobleme?" < docker-compose.yml
Volumes: mehr als nur Datenpersistenz
Named Volumes sind für Datenbankdaten — das ist bekannt. Aber Volumes können mehr. Bind Mounts für Entwicklung, tmpfs für temporäre Daten, Volume-Sharing zwischen Services:
services:
web:
volumes:
# Named Volume für Uploads
- uploads:/app/uploads
# Bind Mount für Entwicklung (nur im Override)
- ./src:/app/src:ro
# tmpfs für temporäre Dateien
- type: tmpfs
target: /tmp
worker:
volumes:
# Dasselbe Upload-Volume wie der Web-Service
- uploads:/app/uploads:ro
volumes:
uploads:
driver: local
Der Worker kann Uploads lesen (weil er dasselbe Volume mountet), aber nicht schreiben (weil :ro — read-only). Das ist eine saubere Permissions-Trennung auf Volume-Ebene.
Volume-Backup: Named Volumes leben außerhalb des Container-Lifecycles — sie überleben docker compose down, aber nicht docker compose down -v. Wenn du Volumes sichern willst, brauchst du einen expliziten Backup-Workflow. Claude Code kann dir dabei helfen, ein Backup-Script zu schreiben: claude "Schreibe ein Bash-Script das alle benannten Volumes in docker-compose.yml sichert und in ein Verzeichnis mit Zeitstempel archiviert" < docker-compose.yml
Externe Volumes und Netzwerke
Manchmal willst du Volumes oder Netzwerke nutzen, die außerhalb des Compose-Projekts existieren — etwa ein Netzwerk, das von einem anderen Compose-Projekt erstellt wurde, oder ein Volume, das manuell erstellt wurde:
volumes:
shared-data:
external: true # muss vor docker compose up existieren
networks:
shared-network:
external: true
name: myproject_app-network
claude "Ich will zwei Compose-Projekte miteinander verbinden, sodass Services
aus Projekt A mit Services aus Projekt B kommunizieren können. Wie richte
ich das sicher ein?"
4. Environment-Variablen und Secrets verwalten
Secrets in Docker Compose falsch zu verwalten ist einer der häufigsten Sicherheitsfehler in der Cloud-Entwicklung. Datenbankpasswörter hart in die docker-compose.yml kodiert, API-Keys in Umgebungsvariablen die im docker inspect-Output sichtbar sind — das sind keine theoretischen Risiken. Claude Code hilft, von Anfang an richtig zu konfigurieren.
Die .env-Datei und ihre Grenzen
Der erste Schritt: Konfigurationswürter aus der Compose-Datei in eine .env-Datei auslagern. Docker Compose lädt automatisch eine .env-Datei aus dem Verzeichnis, in dem du docker compose up ausführst:
# .env
POSTGRES_PASSWORD=<sichere-zufallszeichenkette>
REDIS_PASSWORD=<anderer-sicherer-wert>
API_KEY=geheimer-api-key
DOMAIN=meine-domain.de
# docker-compose.yml
services:
db:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
web:
environment:
DATABASE_URL: postgresql://app:${POSTGRES_PASSWORD}@db:5432/appdb
Die .env-Datei kommt in .gitignore — das ist Pflicht, kein Vorschlag. Claude Code prüft das bei der Analyse:
claude "Analysiere dieses Setup auf Sicherheitsprobleme. Sind irgendwelche
Secrets direkt in der Compose-Datei, in Dockerfiles oder in anderen
versionierten Dateien?" < docker-compose.yml
Docker Secrets für produktionsreife Setups
Für Produktionsumgebungen ist die .env-Datei nicht ausreichend. Docker hat ein eigenes Secrets-System, das Werte als temporäre Dateien in /run/secrets/ im Container bereitstellt — statt als Umgebungsvariablen, die überall sichtbar sind:
services:
db:
image: postgres:16-alpine
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_USER: app
POSTGRES_DB: appdb
web:
secrets:
- db_password
- api_key
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
API_KEY_FILE: /run/secrets/api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
Der entscheidende Unterschied: Secrets erscheinen nicht in docker inspect, nicht in docker compose config-Ausgaben und nicht in den Prozess-Umgebungsvariablen. Sie sind als Dateien im Container verfügbar — lesbar nur vom Prozess, der sie braucht.
Vorsicht mit Compose-Variablen: ${VARIABLE} in der docker-compose.yml ist eine Compose-Variable — sie wird beim docker compose up-Aufruf durch den Wert aus .env ersetzt. Das bedeutet: der Wert steht danach in der Container-Umgebung und ist per docker inspect sichtbar. Für echte Secrets: Docker Secrets nutzen, nicht Umgebungsvariablen.
Mehrere .env-Dateien für verschiedene Umgebungen
In der Praxis hast du verschiedene Konfigurationen für Entwicklung, Staging und Produktion. Docker Compose unterstützt das mit --env-file:
# Entwicklung
docker compose --env-file .env.development up
# Staging
docker compose --env-file .env.staging up -d
# Produktion
docker compose --env-file .env.production up -d
claude "Ich habe eine docker-compose.yml die in Entwicklung, Staging und Produktion
verwendet werden soll. Erstelle drei .env-Dateien mit den nötigen Unterschieden
und zeige mir, welche Werte ich für jede Umgebung setzen muss." < docker-compose.yml
5. Health Checks und Abhängigkeiten
Health Checks sind einer der wichtigsten — und am häufigsten fehlenden — Teile eines produktionsreifen Docker-Compose-Setups. Ohne Health Checks weiß Docker nur, ob ein Container läuft, nicht ob der Service darin funktioniert. Das führt zu race conditions beim Start, zu unentdeckten Ausfällen im Betrieb und zu falschen Statusanzeigen in Monitoring-Systemen.
Health Checks richtig schreiben
Ein guter Health Check testet, ob der Service wirklich bereit ist — nicht nur ob der Prozess läuft. Für verschiedene Service-Typen sieht das unterschiedlich aus:
services:
# Webserver: HTTP-Endpunkt prüfen
web:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# PostgreSQL: pg_isready nutzen
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
# Redis: PING-Kommando
cache:
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
# MySQL: mysqladmin nutzen
mysql:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
Der start_period-Parameter ist besonders wichtig: Er gibt dem Service Zeit zum Hochfahren, bevor Docker anfängt, Health Checks zu zählen. Eine Datenbank die beim ersten Start Migrations ausführt, braucht länger als eine die nur startet.
depends_on mit Health-Check-Bedingungen
Erst wenn Health Checks richtig konfiguriert sind, macht depends_on mit Bedingungen Sinn:
services:
web:
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
migrations:
condition: service_completed_successfully
migrations:
image: myapp:latest
command: ["npm", "run", "db:migrate"]
depends_on:
db:
condition: service_healthy
# kein restart: migrations laufen einmal und sind dann fertig
Das migrations-Service-Muster ist besonders nützlich: Du kannst Datenbankmigrationen als eigenen Service definieren, der einmal läuft (condition: service_completed_successfully) und erst dann lässt Docker den Web-Service starten. Kein manuelles Warten, keine race conditions.
claude "Mein Compose-Setup startet Services in der falschen Reihenfolge.
Die Web-App versucht auf die Datenbank zuzugreifen bevor sie bereit ist.
Wie richte ich Health Checks und depends_on korrekt ein?" < docker-compose.yml
Health Checks im Betrieb überwachen
Laufende Health-Check-Status prüfen:
docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Health}}"
Wenn ein Health Check fehlschlägt:
docker inspect <container-name> --format '{{json .State.Health}}' | python3 -m json.tool | \
claude "Was sagt mir dieser Health-Check-Output? Warum schlägt der Check fehl?"
Health Check für eigene Anwendungen: Wenn du einen /health-Endpunkt in deiner Anwendung brauchst, hilft Claude Code beim Implementieren: claude "Implementiere einen /health-Endpunkt für meine Express.js-App der Datenbankverbindung und Redis-Verbindung prüft und strukturiertes JSON zurückgibt". Ein guter Health-Check-Endpunkt prüft alle kritischen Abhängigkeiten und gibt klare Statusinformationen zurück.
6. Development vs. Production Compose-Setup
Das größte Anti-Pattern in Docker-Compose-Setups: eine einzige Datei für Entwicklung und Produktion. In der Entwicklung willst du Hot Reload, Debug-Ports, pgAdmin, minimale Security-Konfiguration. In der Produktion willst du Ressourcengrenzen, kein Bind-Mounting, keine Debug-Tools, maximale Isolation. Mit einer Datei bist du immer ein Kompromiss.
Die richtige Lösung: Docker Compose Overrides. Du hast eine Basis-Datei (docker-compose.yml), und jeweils eine Override-Datei für Entwicklung und Produktion. Docker Compose merged diese automatisch:
# Für Entwicklung (merge automatisch docker-compose.yml + docker-compose.override.yml)
docker compose up
# Für Produktion (explizit angeben)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Basis-Datei: die gemeinsame Struktur
# docker-compose.yml (Basis)
services:
web:
build:
context: .
environment:
NODE_ENV: ${NODE_ENV:-development}
DATABASE_URL: postgresql://app:${DB_PASSWORD}@db:5432/appdb
depends_on:
db:
condition: service_healthy
networks:
- app-network
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: appdb
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
volumes:
postgres-data:
networks:
app-network:
Development Override: Komfort und Geschwindigkeit
# docker-compose.override.yml (Entwicklung, automatisch geladen)
services:
web:
build:
target: development
volumes:
# Live Reload: Source-Code direkt mounten
- ./src:/app/src
- ./public:/app/public
ports:
- "3000:3000"
- "9229:9229" # Node.js Debug-Port
environment:
NODE_ENV: development
DEBUG: "app:*"
db:
ports:
- "5432:5432" # Datenbankzugriff von außen für Tools
# pgAdmin nur in Entwicklung
pgadmin:
image: dpage/pgadmin4:latest
environment:
PGADMIN_DEFAULT_EMAIL: dev@local.test
PGADMIN_DEFAULT_PASSWORD: dev
ports:
- "5050:80"
networks:
- app-network
# Mailcatcher für E-Mail-Tests
mailcatcher:
image: sj26/mailcatcher:latest
ports:
- "1025:1025" # SMTP
- "1080:1080" # Web UI
networks:
- app-network
Production Override: Sicherheit und Stabilität
# docker-compose.prod.yml (Produktion, explizit angeben)
services:
web:
build:
target: production
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
memory: 256M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
db:
restart: unless-stopped
deploy:
resources:
limits:
memory: 1G
# kein Port nach außen in Produktion
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
networks:
- app-network
claude "Mein Development-Setup funktioniert gut, aber ich will dasselbe Projekt
jetzt in Produktion deployen. Welche Änderungen brauche ich in der
Production-Konfiguration und was muss ich unbedingt beachten?" < docker-compose.yml
Multi-Stage Dockerfiles für Development und Production
Das Override-Pattern funktioniert am besten in Kombination mit Multi-Stage Dockerfiles, die verschiedene Targets für Entwicklung und Produktion definieren:
claude "Erstelle ein Multi-Stage Dockerfile für meine Node.js-App mit:
- development-Stage mit allen devDependencies und nodemon
- build-Stage für TypeScript-Kompilierung
- production-Stage nur mit nötigen Runtime-Dateien
Die Stages sollen mit den docker-compose.override.yml- und
docker-compose.prod.yml-Targets übereinstimmen."
Das Ergebnis ist ein Setup, das in Entwicklung und Produktion aus denselben Quellen baut — aber mit passenden Konfigurationen für jede Umgebung. Kein "funktioniert bei mir, nicht auf dem Server".
Docker Compose mit Claude Code ist mehr als Konfigurationsdateien schreiben. Es ist ein systematischer Workflow: Claude Code analysiert dein bestehendes Setup, identifiziert Sicherheitsrisiken, generiert korrekte Health Checks, erklärt was jede Konfigurationsoption tut, und hilft beim Debuggen wenn etwas nicht startet. Der Unterschied zwischen Stunden mit der Dokumentation und einem produktionsreifen Setup in Minuten.
Zwei verwandte Artikel die auf diesem Thema aufbauen:
- Claude Code Debugging — wie du mit Claude Code Bugs systematisch findest und behebst
- Claude Code für Unternehmen — Deployment, Zugriffskontrolle und Team-Workflows auf Unternehmensebene
Claude Code Mastery — von Docker Compose bis zum produktiven Agenten
Docker Compose ist eine Stärke von Claude Code — aber nicht die einzige. Im Kurs lernst du Agents, MCP-Server, Hooks, Multi-Agent-Workflows und mehr. Vollständig auf Deutsch, einmalig bezahlt.
Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage RückgaberechtKurs · Claude Code Mastery
Von Docker Compose zum produktiven AI-Agenten
Docker Compose. Debugging. Agents. MCP. Hooks. Multi-Agent-Workflows. Alles auf Deutsch, einmalig bezahlt — kein Abo, keine Plattformabhängigkeit.
Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht