Zum Inhalt springen
← Alle Beiträge
· 4 Min. Lesezeit·Emre Yurtbay

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.

DockerDocker ComposeHealthcheckSelf-HostingDevOpsPostgresContainer

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.

Projekt besprechen