Wenn eine Webapp aus mehr als „nur“ einem Server besteht, wird das lokale Setup oft mühsam: Datenbank installieren, passende Version finden, Ports freihalten, Umgebungsvariablen setzen und am Ende läuft es trotzdem nur auf einem Rechner. Genau hier hilft Docker Compose. Es beschreibt eine kleine Landschaft aus Diensten (z. B. App, Datenbank, Cache) in einer Datei und startet sie als Einheit.
Wichtig: Compose ist kein Cloud-Tool und kein Ersatz für gutes Deployment. Es ist vor allem ein praktischer Helfer für lokale Entwicklung, Tests und kleine Staging-Setups. Wer bereits mit Containern arbeitet, bekommt damit einen klaren, wiederholbaren Startknopf für die eigene Umgebung.
Wofür Docker Compose in der Praxis wirklich gut ist
Compose löst typische Alltagsprobleme in Teams und Projekten:
- Reproduzierbarkeit: Alle starten dieselben Services mit denselben Versionen.
- Einheitliche Start-/Stop-Befehle statt individueller Readmes mit 20 Schritten.
- Mehrere Abhängigkeiten gleichzeitig: Datenbank, Redis, Mailcatcher, Worker.
- Saubere Trennung vom Host-System: weniger „Welche PHP-Version hast du?“
Besonders in Projekten mit Backend + DB ist Compose ein Gewinn. Wenn zusätzlich ein Frontend-Build, Queue-Worker oder ein lokaler S3-Ersatz nötig ist, zahlt sich das noch stärker aus.
Compose vs. „nur Docker run“
Mit docker run lässt sich ein Container starten, aber sobald mehrere Container zusammenarbeiten, wird es unübersichtlich: Netzwerke, Volumes, Abhängigkeiten und Umgebungsvariablen müssen je Container wiederholt werden. Compose bündelt das in einer Konfiguration und verwaltet die Abhängigkeiten.
Compose ist keine Magie – nur eine Beschreibung
Compose führt im Kern zusammen, was Docker ohnehin kann: Container, Netzwerke und Volumes. Der Vorteil ist nicht „mehr Power“, sondern weniger manuelle Arbeit und weniger vergessene Schritte.
Die Compose-Datei lesen: die wichtigsten Bausteine
Eine docker-compose.yml beschreibt Services. Ein Service ist meist „ein Container“, z. B. eine Postgres-DB oder das Backend. Dazu kommen Netzwerke und Volumes. Ein minimalistisches Beispiel für eine typische Webapp:
services:
app:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://app:secret@db:5432/app
depends_on:
- db
db:
image: postgres:16
environment:
- POSTGRES_USER=app
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=app
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Dieses Setup zeigt die Kernideen:
- services: „app“ und „db“ werden gemeinsam gestartet.
- ports: Port-Mapping vom Host in den Container.
- environment: Umgebungsvariablen (Konfiguration ohne Codeänderung).
- volumes: Daten bleiben erhalten, auch wenn Container neu gebaut werden.
Images, Build und Tags: was wird eigentlich gestartet?
Bei image: postgres:16 wird ein fertiges Image aus einem Registry (oft Docker Hub) genutzt. Bei build: . wird ein Image aus einem Dockerfile im Projekt gebaut. Für Teams ist es hilfreich, Versionen zu pinnen (z. B. postgres:16 statt latest), damit nicht plötzlich nach einem Update etwas bricht.
Ports richtig verstehen (und typische Konflikte vermeiden)
Ein Port-Mapping wie 8080:8080 bedeutet: Host-Port 8080 wird zum Container-Port 8080 durchgereicht. Wenn Port 8080 schon belegt ist, startet der Service nicht. Dann hilft es, nur den Host-Port anzupassen (z. B. 8081:8080) oder im Zweifel gar keine Ports nach außen zu öffnen, wenn nur andere Container zugreifen.
Persistente Daten: Volumes ohne Überraschungen
Datenbanken sind das klassische Beispiel: Ohne Volume verschwinden Daten beim Entfernen des Containers. Mit einem benannten Volume (z. B. db_data) bleiben Daten erhalten. Das ist praktisch, kann aber auch zu „Geisterfehlern“ führen, wenn alte Daten oder alte DB-Schemas liegen bleiben.
Wann ein Volume bewusst gelöscht werden sollte
Typische Situationen:
- Ein Migration-Fehler hat die lokale DB in einen kaputten Zustand gebracht.
- Das Projekt wurde auf eine neue DB-Version umgestellt und der alte Datenstand passt nicht mehr.
- Tests sollen von einem definierten Startzustand laufen.
Dann hilft es, das Volume zu entfernen und die Daten frisch aufzubauen. In Datenbank-Projekten ist dafür ein sauberes Migrations-Konzept wichtig; dazu passt der Artikel Database Migrations verstehen.
Bind Mounts vs. benannte Volumes
Für Quellcode wird oft ein Bind Mount genutzt (Host-Ordner wird in den Container gemountet), damit Änderungen sofort sichtbar sind. Für Datenbanken sind benannte Volumes meist robuster, weil Dateirechte und Performance in der Praxis weniger Ärger machen.
Netzwerk & Service-Namen: warum „localhost“ im Container oft falsch ist
Ein häufiger Anfängerfehler: Die App im Container verbindet sich mit localhost zur Datenbank. Im Container bedeutet localhost aber „dieser Container selbst“. Die Datenbank läuft jedoch in einem anderen Container.
Compose löst das elegant: Services können sich über ihren Servicenamen erreichen. Wenn der DB-Service db heißt, ist der Hostname im Container einfach db. So wird die Konfiguration stabiler und unabhängig davon, ob Ports nach außen gemappt sind.
depends_on ist kein „Warte-bis-fertig“
depends_on startet Services in einer Reihenfolge, garantiert aber nicht, dass die Datenbank schon „bereit“ ist (z. B. Verbindungen annimmt). Für robuste Setups braucht die App einen Retry (kurz: erneut versuchen) oder einen Healthcheck. Das ist kein Compose-Problem, sondern normales verteiltes Verhalten: Prozesse starten, aber sind nicht sofort nutzbar.
Konfiguration sauber halten: Umgebungsvariablen und Geheimnisse
Compose-Konfigurationen werden schnell unübersichtlich, wenn Zugangsdaten, URLs und Feature-Schalter hart in der Datei stehen. Besser ist ein klarer Umgang mit Environment Variables (Umgebungsvariablen): Werte werden außerhalb des Codes gesetzt, der Code liest sie beim Start.
.env: bequem, aber mit Regeln
In vielen Projekten liegt eine .env-Datei, die lokale Werte enthält. Wichtig ist dabei die Team-Praxis: Eine .env.example als Vorlage einchecken, echte Secrets nicht. Wer die Grundlagen sauber aufbauen will, findet dazu einen passenden Einstieg in Environment Variables verstehen – .env sicher nutzen.
Passwörter lokal: realistisch, aber nicht übertreiben
Lokal darf ein Passwort wie secret stehen, solange klar ist: Das ist nur für Entwicklung. Trotzdem hilft es, Konfiguration nicht im Code zu verstecken. Dann lässt sich später leichter auf echte Secrets (z. B. im Deployment) umstellen.
Ein kleines Praxisbeispiel: Backend + Postgres + Admin-UI
Ein typisches Setup besteht nicht nur aus App und DB, sondern auch aus einem Tool zum Nachsehen. Eine Admin-UI (z. B. pgAdmin oder Adminer) ist praktisch, um Tabellen zu prüfen oder schnell Daten zu ändern. In Compose ist das ein dritter Service, der im selben Netzwerk läuft und die DB über den Service-Namen erreicht.
Wenn es in Teams hakt: die häufigsten Stolperfallen
| Problem | Typische Ursache | Pragmatische Lösung |
|---|---|---|
| Port ist belegt | Ein anderer Dienst nutzt bereits den Host-Port | Host-Port ändern oder Port-Mapping weglassen |
| App findet DB nicht | In der App steht „localhost“ statt Service-Name | DB-Hostname auf „db“ (oder Service-Name) setzen |
| „Permission denied“ bei Dateien | Bind Mount + Dateirechte auf dem Host | Benutzer/UID im Container prüfen, Mount-Strategie anpassen |
| Alte Daten bleiben „kleben“ | Volume enthält Altlasten | Volume bewusst löschen und Daten neu aufbauen |
Kurzer Ablauf für ein stabiles Setup im Projekt
Wer Compose neu ins Projekt bringt oder ein bestehendes Setup aufräumt, kommt mit einem klaren Ablauf schneller zu einem Ergebnis:
- Services identifizieren: App, DB, Cache, Worker, Tools.
- Versionen pinnen (z. B. Postgres 16 statt latest).
- Volumes für persistente Daten definieren, Code als Bind Mount.
- Verbindungen über Service-Namen konfigurieren (nicht über localhost).
- Eine
.env.examplepflegen und lokale Werte dokumentieren. - Ein Startkommando für alle im Readme festhalten (z. B. „compose up“).
Ein paar Befehle, die im Alltag wirklich helfen
- docker compose up -d: startet alles im Hintergrund.
- docker compose logs -f: zeigt Live-Logs aller Services.
- docker compose exec app sh: öffnet eine Shell im App-Container.
- docker compose down: stoppt und entfernt Container (Volumes bleiben).
- docker compose down -v: entfernt zusätzlich Volumes (Achtung: Daten weg).
Wie Compose mit Tests, CI und Deployments zusammenspielt
Compose ist ideal, um Tests lokal „wie in echt“ laufen zu lassen: eine frische Datenbank, ein isoliertes Netzwerk, gleiche Abhängigkeiten. In CI (automatisierte Builds/Tests) wird oft ebenfalls in Containern getestet, aber nicht jedes Team nutzt dort Compose 1:1. Das Ziel bleibt gleich: reproduzierbare Umgebungen.
Mehrere Umgebungen: lokal anders als Produktion
Produktion nutzt häufig Orchestrierung (z. B. Kubernetes) oder Plattform-Services. Trotzdem lohnt sich Compose, weil es die lokale Realität stabil macht. Wichtig ist, Konfiguration über Umgebungsvariablen zu steuern und keine produktionsspezifischen Annahmen in das lokale Setup zu „backen“.
Wenn APIs involviert sind: lokale Sicherheit nicht vergessen
Sobald Webhooks, Tokens oder Cookies lokal getestet werden, sollte das Setup nicht nur „laufen“, sondern auch typische Sicherheits-Mechanismen abbilden. Passende Vertiefung bieten je nach Bedarf HTTP Cookies verstehen oder Webhooks sicher verifizieren.

