Docker Compose von Grund auf: Multi-Service-Stack mit Healthchecks und eigenem Netzwerk
Ein lauffähiges Compose-Minimalbeispiel mit Postgres und Adminer – inklusive Healthcheck, depends_on auf service_healthy, eigenem Netzwerk und Stolperfallen.
Sobald eine Anwendung aus mehr als einem Container besteht – App plus Datenbank, dazu vielleicht ein Cache –, wird das Hantieren mit einzelnen docker run-Befehlen mühsam. Docker Compose beschreibt den gesamten Stack in einer einzigen YAML-Datei: Welche Services laufen, in welchem Netzwerk sie sich finden, wann ein Service als „gesund“ gilt und in welcher Reihenfolge gestartet wird. Dieser Beitrag baut ein minimales, aber realistisches Beispiel auf – Postgres plus Adminer – und zeigt die drei Bausteine, die jeder Mehr-Service-Stack braucht.
Was Compose macht – und wann es passt
Compose ist das Werkzeug für mehrere Container auf einem Host. Es eignet sich hervorragend für lokale Entwicklung und für überschaubare Single-Host-Deployments (ein VPS, ein Self-Hosting-Server). Für Orchestrierung über mehrere Knoten greift man zu Kubernetes oder Docker Swarm – aber für „App + Datenbank auf einer Maschine“ ist Compose das richtige, schlanke Mittel.
Drei Dinge möchte man fast immer: Die Services sollen sich untereinander erreichen (eigenes Netzwerk), ein abhängiger Service soll erst starten, wenn seine Datenbank wirklich bereit ist (Healthcheck + depends_on), und abgestürzte Container sollen automatisch neu starten (Restart-Policy). Genau das deckt das folgende Beispiel ab.
Der minimale Stack
Eine einzige Datei compose.yaml genügt:
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: <YOUR_PASSWORD>
POSTGRES_DB: appdb
volumes:
- db-data:/var/lib/postgresql/data
networks:
- appnet
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
adminer:
image: adminer:5
environment:
ADMINER_DEFAULT_SERVER: db
ports:
- "8080:8080"
networks:
- appnet
restart: unless-stopped
depends_on:
db:
condition: service_healthy
networks:
appnet:
volumes:
db-data:
Starten:
docker compose up -d
docker compose ps
Nach wenigen Sekunden öffnen Sie http://localhost:8080, melden sich mit app / <YOUR_PASSWORD> an der Datenbank appdb an – und sehen die laufende Postgres-Instanz. Adminer trägt als Server bereits db ein, weil wir ADMINER_DEFAULT_SERVER gesetzt haben.
Die drei Bausteine
Eigenes Netzwerk. Der Block networks: appnet: legt ein benutzerdefiniertes Bridge-Netz an; beide Services treten ihm bei. Auf einem gemeinsamen Compose-Netzwerk erreichen sich Container über den Servicenamen als Hostname. Deshalb verbindet sich Adminer nicht zu localhost, sondern zu db – das ist der DNS-Name des Datenbank-Containers. localhost würde innerhalb des Adminer-Containers auf den Adminer-Container selbst zeigen, nicht auf Postgres.
Healthcheck. Der healthcheck-Block sagt Docker, wie es feststellt, ob Postgres bereit ist. pg_isready ist genau dafür gedacht und im Postgres-Image enthalten; es liefert Exit-Code 0, sobald der Server Verbindungen annimmt. interval ist der Abstand zwischen den Prüfungen, retries die Zahl der Fehlversuche bis „unhealthy“, und start_period ein Kulanzfenster beim Start, in dem Fehlschläge noch nicht zählen. Den Zustand sehen Sie in der Spalte STATUS von docker compose ps (healthy / unhealthy).
Startreihenfolge. depends_on mit condition: service_healthy sorgt dafür, dass Adminer erst startet, wenn der Healthcheck von db grün ist – nicht schon, wenn der Container bloß gestartet wurde. Das ist der entscheidende Unterschied (siehe Stolperfalle 1).
Dazu kommt die Restart-Policy. restart: unless-stopped startet einen Container nach einem Absturz oder einem Neustart des Docker-Daemons automatisch neu – aber nur, solange Sie ihn nicht selbst angehalten haben. Für dauerhaft laufende Dienste ist das die übliche Wahl; always würde einen bewusst gestoppten Container nach einem Daemon-Neustart wieder hochfahren, was selten gewünscht ist.
Der Ablauf in der Übersicht:
docker compose up -d
|
v
+-------------+ start_period +----------------+
| db | ---------------> | healthcheck: |
| postgres:17 | pg_isready ... | retries 5 ... |
+-------------+ +-------+--------+
| healthy
v
depends_on: db: condition: service_healthy
|
v
+----------------+
| adminer | :8080
| server = "db" |
+----------------+
Drei typische Stolperfallen
1. depends_on allein wartet nur auf den Start, nicht auf Bereitschaft. Ohne condition: service_healthy startet Docker den abhängigen Service, sobald der Container des anderen läuft – die Datenbank ist dann aber oft noch in der Initialisierung. Erst die Kombination aus Healthcheck am db-Service und condition: service_healthy am abhängigen Service garantiert echte Bereitschaft.
2. Das $$ im Healthcheck. Compose interpretiert $VARIABLE selbst und würde ${POSTGRES_USER} schon beim Einlesen der Datei ersetzen (zu einem leeren Wert). Mit $$ schreiben Sie ein literales $ in die Container-Anweisung, sodass die Variable erst im Container zur Laufzeit aufgelöst wird. Daher pg_isready -U $${POSTGRES_USER}.
3. Das obsolete version:. Ältere Anleitungen beginnen mit version: "3.8". In Compose v2 ist dieser Schlüssel überflüssig und erzeugt nur noch eine Warnung – lassen Sie ihn weg. Compose validiert ohnehin gegen die aktuelle Spezifikation.
Wie es weitergeht
Damit steht ein sauberes Muster, das sich auf jeden weiteren Service überträgt: eigenes Netz, Healthcheck, depends_on. Naheliegende nächste Schritte sind eine eigene App im Stack mit einem Multi-Stage-Dockerfile, das Auslagern der Zugangsdaten in eine .env-Datei und der Blick in die vollständige Compose-File-Referenz sowie in den Abschnitt zur Startreihenfolge.