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

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
| Befehl | Wirkung |
|---|---|
docker-compose up -d | Alle Services im Hintergrund starten |
docker-compose ps | Laufstatus anzeigen |
docker-compose logs -f api | API-Logs verfolgen |
docker-compose down | Container stoppen und entfernen |
docker-compose down -v | Container und Volumes entfernen |
docker-compose restart api | API neu starten |
docker-compose build api | API-Image neu bauen |
Diese Befehle decken 90 % des Alltags ab. Rest (exec, cp, top) bei Bedarf in der Doku nachschlagen.
Fazit
Effizienz im Vergleich:
| Aktion | Traditionell | Mit Compose |
|---|---|---|
| Onboarding neuer Kollegen | 4–8 Stunden | 5 Minuten (clone + up) |
| Projektwechsel | Config ändern, Services stoppen, neu starten | Verzeichnis wechseln, andere Compose-Datei |
| Umgebung aufräumen | Manuell deinstallieren, Zombie-Prozesse suchen | Ein Befehl, Container und Volumes weg |
| Team-Konsistenz | Jeder Rechner anders | Einheitliche 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
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
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
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
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
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?
Garantiert depends_on, dass ein Service bereit ist?
Wie erreicht ein Container Services auf dem Host?
Wo liegen die Daten? Gehen sie beim Löschen des Containers verloren?
Wie verwaltet man Multi-Environment-Konfiguration (dev/test/prod)?
Was tun, wenn ein Port belegt ist?
9 Min. Lesezeit · Veröffentlicht am: 9. Apr. 2026 · Aktualisiert am: 14. Juli 2026
Docker Praxisleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Docker-Build beschleunigen: Cache nutzen für 10x schnellere Builds – Praxisleitfaden
Layer-Cache, .dockerignore und Dockerfile-Optimierung: Build-Zeit von 10 Minuten auf 30 Sekunden. Mit Codebeispielen und BuildKit-Cache-Mounts für Fortgeschrittene.
Teil 7 von 38
Nächster
Docker Compose Service-Abhängigkeiten: Healthcheck-Konfiguration löst Startreihenfolge-Probleme mit Datenbanken
Ausführliche Anleitung zu depends_on und healthcheck in Docker Compose – mit Praxisbeispielen gegen Anwendungsstartfehler, wenn die Datenbank noch nicht bereit ist; inklusive vollständiger PostgreSQL- und MySQL-Konfigurationsvorlagen
Teil 9 von 38
Ähnliche Beiträge
Docker vs. virtuelle Maschine: Performance-Unterschiede und Szenario-Auswahl in 5 Minuten

Docker vs. virtuelle Maschine: Performance-Unterschiede und Szenario-Auswahl in 5 Minuten
Docker-Installation ohne Fallstricke (2025): Vollständige Lösungen von permission denied bis zum erfolgreichen Start


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen