Design wechseln

Docker Compose Multi-Service-Orchestrierung: Lokale Entwicklungsumgebung mit einem Befehl starten

Easton editorial illustration: route-map drafting table

Am ersten Nachmittag im neuen Job starrte ich auf den fünften Fehlerdialog – MySQL-Port belegt, Redis-Version inkompatibel, RabbitMQ ließ sich nicht verbinden. Ein Kollege warf einen Blick auf meinen Bildschirm und seufzte: „Wie lange installierst du schon?”

„Seit 9 Uhr morgens.”

Vier Stunden. Drei Datenbanken installieren – vier Stunden. Und das war erst der Anfang; ElasticSearch und MongoDB kamen noch.

Da wurde mir klar: Die lokale Entwicklungsumgebung ist eine Falle. Eine tiefe, unsichtbare Falle.

Später stieg unser Team auf Docker Compose um. Neue Kollegen klonen das Repo, docker-compose up -d, fünf Minuten – alle Services laufen. MySQL, Redis, RabbitMQ, API, Web, ein Befehl, sauber. Projektwechsel? Anderes Verzeichnis, andere Compose-Datei. Aufräumen? docker-compose down -v, Container und Volumes weg, keine Spuren.

In diesem Artikel geht es um genau diese Wende: Wie Sie mit Docker Compose mehrere Services orchestrieren und die lokale Entwicklungsumgebung von „Albtraum” zu „ein Befehl” machen.

Warum Multi-Service-Orchestrierung nötig ist

Vor zehn Jahren, bei Monolithen, war die Umgebung simpel – JDK installieren, DB-Connection-String setzen, fertig. Heute sieht das anders aus.

Die meisten Projekte sind Microservices oder mindestens Frontend/Backend-getrennt. Eine typische lokale Umgebung braucht: Web-Frontend, API-Backend, MySQL für Geschäftsdaten, Redis für Cache und Session, RabbitMQ für asynchrone Nachrichten. Manche Projekte ergänzen ElasticSearch für Suche und MongoDB für Logs.

Dann beginnt das Problem.

Der Schmerz manueller Installation

Auf jedem Rechner alles neu installieren. MySQL-Version wählen – 5.7 oder 8.0? Falsche Version, inkompatible SQL-Syntax. Redis-Port konfigurieren – Standard 6379, aber vielleicht schon belegt? RabbitMQ braucht Erlang … allein die RabbitMQ-Anleitung kostete mich eine halbe Stunde.

Danach kommen Versionskonflikte. Ein anderes Projekt hinterließ alte MySQL-Daten. Port-Kollision, Start schlägt fehl. Stundenlang debuggen – ein Zombie-Prozess lief noch.

Am frustrierendsten ist der Projektwechsel. Projekt A fertig, weiter zu Projekt B. A blockiert Port 3306, B will auch 3306. Config anpassen oder A stoppen. Zwei Tage später zurück zu A – Config wieder ändern.

Hin und her, ohne Ende.

Der Team-Albtraum

„Bei mir läuft es.”

Jedes Team kennt den Satz. Neuer Kollege klont den Code, richtet die Umgebung ein – läuft nicht. Warum? Altes Projekt nutzte MySQL 5.7, Neuling hat 8.0; altes Redis ohne Passwort, lokales Redis mit Passwort. Config mehrfach geändert – immer noch nicht.

Am Ende hilft ein Senior, ein halber Tag weg.

Der Compose-Ansatz

Docker Compose packt alle Services in Container und verwaltet sie zentral in einer Datei.

Sie müssen nicht wissen, wie jede Datenbank installiert wird, welche Version passt oder welcher Port frei ist. In der Compose-Datei definieren, einmal starten – alles läuft nach Plan. Projektwechsel? Anderes Verzeichnis, andere Compose-Datei. Aufräumen? Ein Befehl, Container und Volumes weg.

Das ist der Unterschied zwischen „PC selbst zusammenbauen” und „Fertigrechner kaufen” – Speicher einstecken, Grafikkarte verkabeln? Erledigt. Einschalten und loslegen.

docker-compose.yml – Kernkonfiguration

Zuerst eine vollständige Konfiguration. Angenommen, das Projekt hat vier Services: Web-Frontend, API-Backend, MySQL-Datenbank, Redis-Cache.

# docker-compose.yml
version: "3.8"  # Compose-Dateiversion, 3.8 unterstützt die meisten Optionen

services:
  # Frontend Web-Service
  web:
    build: ./frontend  # Image aus lokalem frontend-Verzeichnis bauen
    ports:
      - "3000:3000"  # Host 3000 -> Container 3000
    depends_on:
      - api  # Abhängigkeit: api startet zuerst
    environment:
      - API_URL=http://api:8080  # Backend-URL für das Frontend

  # Backend API-Service
  api:
    build: ./backend  # Image aus lokalem backend-Verzeichnis bauen
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redis  # Abhängig von DB und Cache
    environment:
      - DB_HOST=mysql  # DB-Adresse (Containername)
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=dev123  # Dev-Passwort; in Produktion .env nutzen
      - REDIS_HOST=redis
      - REDIS_PORT=6379

  # MySQL-Datenbank
  mysql:
    image: mysql:8.0  # Offizielles Image, kein Build nötig
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=dev123
      - MYSQL_DATABASE=myapp  # Datenbank automatisch anlegen
    volumes:
      - mysql_data:/var/lib/mysql  # Persistenz im Volume

  # Redis-Cache
  redis:
    image: redis:7-alpine  # Alpine-Variante, kleiner
    ports:
      - "6379:6379"

volumes:
  mysql_data:  # Volume für MySQL-Persistenz

Kernfelder erklärt

Unter services definieren Sie alle Services. Jeder Service kann aus drei Quellen kommen: build aus lokalem Code, image als offizielles Image, oder beides kombiniert.

ports mappt Ports. Format: "Host-Port:Container-Port". Web auf 3000, API auf 8080, MySQL auf 3306, Redis auf 6379. Ist der Host-Port belegt, Host-Seite ändern, z. B. "13006:3306" – dann verbinden Sie sich über localhost:13006.

depends_on steuert die Startreihenfolge. MySQL und Redis zuerst, dann API, zuletzt Web. Aber Vorsicht – gleich mehr dazu.

environment setzt Umgebungsvariablen. DB-Passwort, Verbindungsadressen, Ports. In Produktion keine Passwörter in die Datei – .env oder Umgebungsvariablen nutzen.

volumes persistiert Daten. MySQL-Daten landen in mysql_data – Container löschen, Daten bleiben.

Eine häufige Falle

Der Containername ist der Servicename. In der Config verbindet die API mit DB_HOST=mysql, nicht localhost. Warum?

Jeder Container hat sein eigenes Netzwerk. localhost im API-Container zeigt auf den API-Container selbst, nicht auf den Host oder MySQL. Compose erstellt ein internes Netzwerk; Services erreichen sich über Servicenamen. mysql ist die Adresse des MySQL-Containers im Netzwerk.

Beim ersten Compose-File habe ich localhost:3306 geschrieben – keine Verbindung. Erst mit Containername ging es.

Service-Abhängigkeiten und Startreihenfolge

depends_on wirkt simpel: MySQL zuerst, dann API. Aber es gibt einen Haken.

Compose-depends_on garantiert nur Container-Startreihenfolge, nicht Dienst-Bereitschaft. Der MySQL-Container läuft, MySQL selbst initialisiert noch – DB anlegen, Config laden, Port öffnen. Die API verbindet sich sofort – Fehler.

Mir passierte das: docker-compose up, API meldet sofort DB-Verbindungsfehler. Zehn Sekunden später klappt es. MySQL-Container lief, der Dienst war noch nicht bereit.

Lösung 1: Healthcheck

Compose unterstützt Healthchecks in der Config. Erst nach bestandenem Check starten abhängige Services.

services:
  mysql:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s  # alle 5 Sekunden prüfen
      timeout: 3s   # Timeout pro Prüfung
      retries: 10   # 10 Fehlversuche bis unhealthy
    # ... weitere Config

  api:
    depends_on:
      mysql:
        condition: service_healthy  # wartet auf MySQL-Healthcheck

MySQL führt mysqladmin ping aus. Alle 5 Sekunden, maximal 10 Versuche (50 Sekunden). Erst dann startet die API.

Wirksam, aber jeder Service braucht Healthcheck-Config. Manche Images (z. B. Redis) bieten keinen bequemen Prüfbefehl – dann selbst basteln.

Lösung 2: Retry auf Anwendungsebene

Einfacher: Retry-Logik im Code. Verbindung fehlgeschlagen, kurz warten, erneut versuchen. MySQL braucht Zeit – warten, bis bereit.

Exponentielles Backoff ist üblich: 1 s, 2 s, 4 s … Die meisten Datenbanken sind in 30 Sekunden bereit.

In Node.js mit mysql2-Connection-Pool:

const pool = mysql.createPool({
  host: 'mysql',
  port: 3306,
  user: 'root',
  password: 'dev123',
  database: 'myapp',
  waitForConnections: true,  // auf verfügbare Verbindung warten
  connectionLimit: 10,
  queueLimit: 0,
});

In Python mit tenacity:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=10))
def connect_db():
    return mysql.connector.connect(host='mysql', ...)

Welche Lösung wählen?

Healthcheck ist präziser – API startet erst, wenn die DB wirklich bereit ist. Mehr Config-Aufwand pro Datenbank.

Retry auf Anwendungsebene ist einfacher – wenige Codezeilen, Compose unverändert. Nachteil: kurze Phase mit Verbindungsfehlern in den Logs (Ergebnis meist trotzdem ok).

Ich bevorzuge Retry – weniger Aufwand, in den meisten Fällen stabil. Healthcheck als Backup für besonders langsame Services.

Multi-Environment-Konfiguration

Entwicklung, Test, Produktion – unterschiedliche Anforderungen. Dev braucht DB-Ports nach außen für Debugging; Produktion nicht, DB nur im internen Netz.

Alles in einer Datei? Beim Wechsel editieren, zurückeditieren – fehleranfällig.

Compose löst das mit Basisdatei + Override.

Basisdatei: gemeinsame Config

docker-compose.yml enthält alles Gemeinsame – Service-Definitionen, Image-Versionen, internes Netzwerk, Volumes.

# docker-compose.yml (Basis)
version: "3.8"

services:
  web:
    build: ./frontend
    # ports in Override-Datei

  api:
    build: ./backend
    environment:
      - DB_HOST=mysql
      - REDIS_HOST=redis
    # ports nicht gesetzt

  mysql:
    image: mysql:8.0
    volumes:
      - mysql_data:/var/lib/mysql
    # ports nicht gesetzt – Produktion braucht kein Mapping

  redis:
    image: redis:7-alpine

volumes:
  mysql_data:

Port-Mapping fehlt absichtlich – je nach Umgebung unterschiedlich.

Override für Entwicklung

docker-compose.override.yml ergänzt Dev-spezifisches – Port-Mapping, Dev-Passwörter, Debug-Variablen.

# docker-compose.override.yml (Entwicklung)
version: "3.8"

services:
  web:
    ports:
      - "3000:3000"  # Dev: Port nach außen

  api:
    ports:
      - "8080:8080"
    environment:
      - DEBUG=true  # Debug-Modus in Dev

  mysql:
    ports:
      - "3306:3306"  # DB-Port für lokales Debugging
    environment:
      - MYSQL_ROOT_PASSWORD=dev123  # einfaches Dev-Passwort

  redis:
    ports:
      - "6379:6379"

Standardverhalten: Bei docker-compose up merged Compose docker-compose.yml und docker-compose.override.yml. Override überschreibt Basis.

Lokal also einfach docker-compose up – volle Dev-Config.

Override für Produktion

docker-compose.prod.yml für Produktion – keine Port-Exposition, Produktions-Passwörter, externe Services.

# docker-compose.prod.yml (Produktion)
version: "3.8"

services:
  web:
    # kein Port-Mapping – Zugriff über Reverse Proxy (nginx)

  api:
    environment:
      - DB_HOST={{DB_HOST}}  # aus Umgebungsvariable, kein Passwort in Datei
      - DB_PASSWORD={{DB_PASSWORD}}

  mysql:
    # kein Port-Mapping – kein direkter externer Zugriff
    environment:
      - MYSQL_ROOT_PASSWORD={{MYSQL_ROOT_PASSWORD}}

Produktionsstart mit -f:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Beide Dateien werden gemerged; prod überschreibt Basis. DB-Ports bleiben intern, Passwörter aus Umgebungsvariablen.

Umgebungsvariablen injizieren

Produktions-Passwörter gehören nicht in Dateien. Compose liest aus .env oder System-Umgebung.

# .env (nicht in git committen)
DB_HOST=prod-mysql.internal
DB_PASSWORD=super_secret_password_123
MYSQL_ROOT_PASSWORD=another_secret

In der Config mit {{VAR:-default}}-Syntax referenzieren:

environment:
  - DB_HOST={{DB_HOST:-localhost}}  # Fallback localhost
  - DB_PASSWORD={{DB_PASSWORD:-dev123}}

.env nicht versionieren – .env.example mit Platzhaltern, Team kopiert und füllt eigene Werte.

Ein-Klick-Befehle in der Praxis

Config steht – starten, stoppen, debuggen. Diese Befehle reichen für den Alltag.

Alle Services starten

docker-compose up -d

up startet alles. -d = detached (Hintergrund), Terminal bleibt frei. Ohne -d laufen Logs im Terminal; Ctrl+C stoppt.

Compose zieht Images (image), baut Images (build), erstellt Container, startet Services. Erster Start dauert wegen Image-Download; danach geht es schneller.

Status prüfen

docker-compose ps

Listet Container-Status. Beispielausgabe:

NAME                COMMAND             SERVICE   STATUS    PORTS
myapp-web-1         "npm start"         web       running   0.0.0.0:3000->3000/tcp
myapp-api-1         "node index.js"     api       running   0.0.0.0:8080->8080/tcp
myapp-mysql-1       "mysqld"            mysql     running   0.0.0.0:3306->3306/tcp
myapp-redis-1       "redis-server"      redis     running   0.0.0.0:6379->6379/tcp

STATUS running = ok. exited oder error = Start fehlgeschlagen.

Logs ansehen

docker-compose logs -f api

Logs des API-Services. -f = follow, Echtzeit. Ohne -f nur bisherige Logs.

Ohne Servicename zeigt docker-compose logs -f alles – bei viel Output unübersichtlich.

Stoppen und aufräumen

docker-compose down

Stoppt Container, entfernt Container und Netzwerke. Volumes bleiben – MySQL-Daten auch.

Komplett aufräumen inkl. Volumes:

docker-compose down -v

-v löscht Volumes. Nächster Start initialisiert MySQL neu, alle Daten weg. In der Entwicklung oft genutzt – Daten durcheinander, neu anfangen.

Images neu bauen

Code geändert, Image neu bauen:

docker-compose build api

Nur API-Image. Danach Container neu starten:

docker-compose up -d api

Oder in einem Schritt:

docker-compose up -d --build api

--build erzwingt Rebuild auch wenn Image existiert.

Befehls-Übersicht

BefehlWirkung
docker-compose up -dAlle Services im Hintergrund starten
docker-compose psLaufstatus anzeigen
docker-compose logs -f apiAPI-Logs verfolgen
docker-compose downContainer stoppen und entfernen
docker-compose down -vContainer und Volumes entfernen
docker-compose restart apiAPI neu starten
docker-compose build apiAPI-Image neu bauen

Diese Befehle decken 90 % des Alltags ab. Rest (exec, cp, top) bei Bedarf in der Doku nachschlagen.

Fazit

Effizienz im Vergleich:

AktionTraditionellMit Compose
Onboarding neuer Kollegen4–8 Stunden5 Minuten (clone + up)
ProjektwechselConfig ändern, Services stoppen, neu startenVerzeichnis wechseln, andere Compose-Datei
Umgebung aufräumenManuell deinstallieren, Zombie-Prozesse suchenEin Befehl, Container und Volumes weg
Team-KonsistenzJeder Rechner andersEinheitliche Config, identische Umgebung

Der Unterschied ist deutlich.

Wenn Sie noch manuell Datenbanken installieren, Configs anpassen und Port-Konflikte debuggen – probieren Sie Docker Compose. Starten Sie klein: API + MySQL, eine docker-compose.yml, zum Laufen bringen. Dann Redis, RabbitMQ, Multi-Environment dazu.

Im Team: docker-compose.yml und docker-compose.override.yml ins Repo, README mit Startschritten. Neuer Kollege klont, ein Befehl – Entwicklungsumgebung steht.

Das ist zuverlässiger als ein „Umgebungs-Setup-Dokument”. Dokumente veralten – Config-Dateien nicht.

Docker Compose Multi-Service-Orchestrierung in der Praxis

Web, API, MySQL und Redis mit Docker Compose orchestrieren und die lokale Entwicklungsumgebung per Ein-Klick starten

⏱️ Estimated time: 15 min

  1. 1

    Step 1: docker-compose.yml anlegen

    Konfigurationsdatei im Projektroot erstellen:

    ```yaml
    version: "3.8"
    services:
    web:
    build: ./frontend
    ports: ["3000:3000"]
    depends_on: [api]
    api:
    build: ./backend
    ports: ["8080:8080"]
    depends_on: [mysql, redis]
    mysql:
    image: mysql:8.0
    environment:
    MYSQL_ROOT_PASSWORD: dev123
    MYSQL_DATABASE: myapp
    redis:
    image: redis:7-alpine
    ```

    Hinweis: Container kommunizieren über Servicenamen (z. B. DB_HOST=mysql), nicht über localhost
  2. 2

    Step 2: Alle Services starten

    Im Verzeichnis der docker-compose.yml ausführen:

    ```bash
    docker-compose up -d
    ```

    • Beim ersten Start werden Images gezogen – das dauert länger
    • Spätere Starts nutzen vorhandene Images und sind in Sekunden erledigt
    • Ohne -d bleibt das Terminal für Logs belegt
  3. 3

    Step 3: Service-Status prüfen

    Prüfen, ob alle Container normal laufen:

    ```bash
    docker-compose ps
    ```

    • STATUS running bedeutet alles in Ordnung
    • Bei exited oder error mit logs debuggen:
    ```bash
    docker-compose logs api
    ```
  4. 4

    Step 4: Multi-Environment konfigurieren (optional)

    docker-compose.override.yml für die Entwicklungsumgebung anlegen:

    ```yaml
    version: "3.8"
    services:
    mysql:
    ports: ["3306:3306"]
    ```

    • Compose merged Override-Dateien automatisch
    • Produktion mit -f angeben:
    ```bash
    docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
    ```
  5. 5

    Step 5: Umgebung aufräumen

    Alle Container stoppen und entfernen:

    ```bash
    docker-compose down # Volumes behalten
    docker-compose down -v # Volumes löschen (Daten weg)
    ```

    • Beim Projektwechsel down nutzen
    • Bei durcheinander geratenen Daten down -v

FAQ

Was ist der Unterschied zwischen docker-compose.yml und Dockerfile?
Ein Dockerfile definiert die Build-Schritte für ein einzelnes Image, docker-compose.yml beschreibt, wie mehrere Container zusammenarbeiten. Kurz gesagt: Dockerfile baut Images, Compose verwaltet Container. Ein Projekt hat typischerweise ein oder mehrere Dockerfiles (pro Service) und eine docker-compose.yml.
Garantiert depends_on, dass ein Service bereit ist?
Nein. depends_on steuert nur die Startreihenfolge der Container, nicht die Bereitschaft des Dienstes. Der MySQL-Container kann laufen, während MySQL noch initialisiert. Zwei Lösungen: Healthcheck in docker-compose.yml oder Retry-Logik im Anwendungscode. Retry auf Anwendungsebene ist meist einfacher und zuverlässiger.
Wie erreicht ein Container Services auf dem Host?
Über host.docker.internal (Docker Desktop) oder die Host-IP (Linux). Läuft Redis auf dem Host, verbinden Sie sich im Container mit host.docker.internal:6379. Unter Linux braucht host.docker.internal Extra-Konfiguration; alternativ --network host (verliert dann Netzwerk-Isolation).
Wo liegen die Daten? Gehen sie beim Löschen des Containers verloren?
Mit volumes konfigurierte Daten liegen in Docker-Volumes – Container löschen bedeutet nicht Datenverlust. docker-compose down entfernt nur Container und Netzwerke, nicht Volumes. Zum Leeren: docker-compose down -v. In der Entwicklung oft genutzt, wenn die Daten durcheinander sind.
Wie verwaltet man Multi-Environment-Konfiguration (dev/test/prod)?
Basisdatei + Override-Strategie: docker-compose.yml für gemeinsame Konfiguration, docker-compose.override.yml für Entwicklung (Compose lädt sie standardmäßig), docker-compose.prod.yml für Produktion. Start mit -f: docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d.
Was tun, wenn ein Port belegt ist?
Host-Port-Mapping anpassen. MySQL standardmäßig 3306:3306 – ist 3306 lokal belegt, z. B. 13006:3306 und localhost:13006 nutzen. Oder ports weglassen und nur im internen Container-Netzwerk zugreifen – sicherer.

9 Min. Lesezeit · Veröffentlicht am: 9. Apr. 2026 · Aktualisiert am: 14. Juli 2026

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog