Wer heute an mehreren Projekten gleichzeitig arbeitet, kennt das Problem: Ein Node-Server braucht Version 20, der nächste Kunde läuft noch auf 18, dazu kommt eine Postgres-Datenbank, ein Redis-Cache und vielleicht noch ein Mailserver zum Testen. Alles lokal zu installieren und bei jedem Projektwechsel neu zu konfigurieren, kostet Zeit und produziert über kurz oder lang Versionskonflikte. Genau dafür wurde Docker Compose gebaut. Mit einer einzigen YAML-Datei beschreibst du den kompletten Stack und startest ihn mit einem Befehl, reproduzierbar auf jedem Rechner, vom MacBook bis zum CI-Server.
Dieses Tutorial zeigt dir Schritt für Schritt, wie du Docker Compose lokal einrichtest, eine Mehrcontainer-Anwendung mit Web-App, Datenbank und Cache aufsetzt und typische Stolperfallen vermeidest. Wir arbeiten mit den aktuellen Versionen: Docker Desktop 4.94.0 und Docker Compose v5.6.0, beide am 5. beziehungsweise 2. Oktober 2026 veröffentlicht. Laut der Docker-Compose-Release-Seite auf GitHub bringt v5.6.0 unter anderem erste, noch eingeschränkte Unterstützung für Jobs mit, bei der aktuell nur manuell ausgelöste Jobs funktionieren, sowie Erweiterungen für sogenannte Provider-Services. Geplante Jobs sollen folgen, sobald die passende Unterstützung in der Docker Engine selbst verfügbar ist.
Was ist Docker Compose und wofür brauchst du es
Docker Compose ist ein Werkzeug, mit dem du mehrere Container als eine zusammengehörige Anwendung definierst, startest und wieder stoppst. Statt für jeden Dienst einzeln docker run mit zehn Flags aufzurufen, schreibst du eine compose.yaml-Datei, in der jeder Service, jedes Netzwerk und jedes Volume beschrieben ist. Ein Befehl wie docker compose up reicht dann, um die gesamte Umgebung hochzufahren. Die vollständige Syntax ist in der offiziellen Compose-Dateireferenz von Docker dokumentiert, die bei komplexeren Setups als Nachschlagewerk nützlich bleibt.
Der Grundgedanke dahinter ist nicht neu, aber er trifft einen wunden Punkt in fast jedem Entwicklerteam: Infrastruktur als Code, nur eben für die lokale Maschine statt für einen Cloud-Anbieter. Früher landete das Setup-Wissen für ein Projekt in einem Wiki-Artikel, der nach drei Monaten veraltet war, weil niemand ihn pflegte. Mit einer versionierten compose.yaml im selben Repository wie der Anwendungscode ändert sich das: Jede Änderung an der Infrastruktur, etwa ein neuer Cache-Dienst oder eine andere Postgres-Version, läuft durch denselben Code-Review-Prozess wie jede andere Codeänderung auch. Ein Blick in die Commit-Historie der compose.yaml verrät damit nebenbei auch, wie sich die Architektur eines Projekts über die Zeit entwickelt hat, ganz ohne separate Dokumentation. Für neue Teammitglieder ist das oft der schnellste Weg, sich einen Überblick über den tatsächlichen Aufbau einer Anwendung zu verschaffen, schneller als jede nachträglich geschriebene Architektur-Beschreibung.
Die Zahlen zeigen, wie zentral das Werkzeug inzwischen im Alltag von Entwicklerinnen und Entwicklern ist. Laut der Stack-Overflow-Developer-Survey 2026, die am 6. Oktober 2026 veröffentlicht wurde und 30.903 gültige Antworten aus 169 Ländern auswertet, liegt Docker beziehungsweise Docker Compose bei der Nutzung unter Cloud- und Entwicklungswerkzeugen mit 59,1 Prozent an der Spitze, noch vor npm mit 54,6 Prozent und pip mit 42,1 Prozent. Kubernetes kommt in derselben Erhebung auf 26 Prozent, Podman auf 13 Prozent. Docker bleibt damit trotz wachsender Konkurrenz durch Podman das mit Abstand am häufigsten genutzte Container-Werkzeug im Entwickleralltag. Die vollständigen Rohdaten lassen sich auf der Technologie-Seite der Stack-Overflow-Umfrage 2026 nachlesen.
Praktisch heißt das: Du beschreibst deinen Stack einmal, committest die Datei ins Repository, und jeder im Team bekommt exakt dieselbe Umgebung. Das Argument “läuft bei mir lokal” verschwindet, weil “lokal” für alle dasselbe Compose-Setup bedeutet.
Voraussetzungen: Diese Versionen brauchst du
Bevor du startest, solltest du folgende Komponenten in den genannten oder neueren Versionen bereithaben. Die Angaben entsprechen dem Stand Oktober 2026.
| Komponente | Mindestversion | Aktuelle Version (Okt. 2026) | Hinweis |
|---|---|---|---|
| Docker Desktop | 4.40 oder neuer | 4.94.0 | Enthält Docker Engine, Compose und Dashboard |
| Docker Compose | v2.22 (für develop/watch) | v5.6.0 | Ist in Docker Desktop bereits integriert |
| Betriebssystem | Windows 10/11, macOS 13+, aktuelle Linux-Distribution | – | Auf Linux reicht Docker Engine ohne Desktop-GUI |
| Arbeitsspeicher | 8 GB RAM | 16 GB empfohlen | Mehr RAM nötig, je mehr Container parallel laufen |
| Freier Speicherplatz | 10 GB | 20 GB empfohlen | Images und Volumes wachsen schnell |
Du brauchst außerdem einen Terminal-Zugang (PowerShell, Terminal.app oder eine Linux-Shell) und einen Code-Editor. Für dieses Tutorial reicht jeder Editor, der YAML-Dateien sauber einfärbt, etwa Visual Studio Code mit der Docker-Erweiterung. Falls du bereits einen SSH-Key zur Serverabsicherung eingerichtet hast, kannst du dieselbe Maschine später auch für produktive Compose-Deployments nutzen.
Schritt 1: Docker Desktop installieren und prüfen
Lade Docker Desktop von der offiziellen Docker-Website für dein Betriebssystem herunter und installiere es wie jede andere Anwendung. Unter Linux kannst du stattdessen die Docker Engine samt Compose-Plugin über den Paketmanager deiner Distribution installieren, ganz ohne grafische Oberfläche. Eine detaillierte, nach Distribution sortierte Anleitung findest du in der offiziellen Compose-Dokumentation von Docker. Nach der Installation prüfst du im Terminal, ob alles funktioniert:
docker --version
docker compose version
Die Ausgabe sollte in etwa so aussehen:
Docker version 29.1.0, build 8a3c1f2
Docker Compose version v5.6.0
Wichtig: Seit mehreren Jahren ist docker compose (mit Leerzeichen, als Unterbefehl von Docker) der Standard. Der alte, eigenständige Befehl docker-compose mit Bindestrich stammt aus Compose v1 und wird in aktuellen Installationen nicht mehr mitgeliefert. Falls deine Shell docker-compose: command not found ausgibt, ist das kein Fehler, sondern erwartetes Verhalten. Nutze stattdessen durchgehend die Schreibweise mit Leerzeichen.
Schritt 2: Projektordner und erste compose.yaml anlegen
Lege einen neuen Projektordner an und wechsle dorthin:
mkdir compose-tutorial
cd compose-tutorial
touch compose.yaml
Beachte den Dateinamen: Die aktuelle Compose Specification verlangt keinen version:-Schlüssel mehr am Dateianfang. Frühere Tutorials beginnen oft mit version: "3.8", das ist heute überflüssig und wird von modernen Compose-Versionen ignoriert beziehungsweise als veraltet markiert. Nenne die Datei einfach compose.yaml (alternativ wird auch docker-compose.yml noch erkannt, aus Kompatibilitätsgründen).
Als ersten, minimalen Test definierst du einen einzigen Webserver-Dienst:
services:
web:
image: nginx:1.27
ports:
- "8080:80"
Starte den Stack:
docker compose up -d
Rufst du anschließend http://localhost:8080 im Browser auf, siehst du die Standard-Willkommensseite von Nginx. Das -d-Flag startet den Container im Hintergrund (detached), sodass dein Terminal wieder frei ist.
Schritt 3: Einen realistischen Mehrcontainer-Stack bauen
Ein einzelner Nginx-Container zeigt das Prinzip, aber der eigentliche Vorteil von Compose entsteht erst, wenn mehrere Dienste zusammenspielen. Wir bauen jetzt einen klassischen Stack: eine Node.js-Anwendung, eine Postgres-Datenbank und einen Redis-Cache.
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@db:5432/appdb
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:17
environment:
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=appdb
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 5s
timeout: 5s
retries: 5
cache:
image: redis:7
volumes:
db-data:
Hier siehst du bereits die wichtigsten Bausteine im Zusammenspiel: build baut das Image aus einem lokalen Dockerfile, depends_on mit condition: service_healthy sorgt dafür, dass die App erst startet, wenn die Datenbank wirklich bereit ist (nicht nur gestartet, sondern über den Healthcheck als gesund bestätigt), und volumes sorgt dafür, dass Datenbankinhalte einen Container-Neustart überleben.
Schritt 4: Geheimnisse und Konfiguration über .env-Dateien verwalten
Im Beispiel oben taucht ${DB_PASSWORD} auf. Diese Variable liest Compose automatisch aus einer .env-Datei im selben Verzeichnis wie die compose.yaml. Lege sie an:
DB_PASSWORD=ein-sicheres-passwort-hier
NODE_ENV=development
Trage die .env-Datei unbedingt in .gitignore ein, damit sie nicht versehentlich ins Repository gelangt. Teile stattdessen eine .env.example ohne echte Werte, damit Teammitglieder wissen, welche Variablen sie selbst setzen müssen. Für produktive Umgebungen solltest du ohnehin auf einen Secrets-Manager umsteigen statt auf Klartext-Dateien, dazu später mehr im Abschnitt zu Sicherheit.
Schritt 5: Watch-Modus für automatisches Neuladen nutzen
Eine der praktischsten Neuerungen der letzten Compose-Versionen ist das develop-Attribut mit dem watch-Feature, verfügbar seit Docker Compose 2.22.0 laut der offiziellen Compose-Spezifikation zu develop. Damit beobachtet Compose lokale Dateiänderungen und synchronisiert sie automatisch in den laufenden Container, ohne dass du das Image neu bauen musst.
services:
app:
build: .
develop:
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: package.json
Gestartet wird der Watch-Modus mit:
docker compose watch
Änderungen im src-Ordner werden live in den Container gespiegelt, während eine Änderung an package.json einen kompletten Rebuild auslöst, weil sich dabei ja Abhängigkeiten ändern können. Das ersetzt in vielen Fällen aufwendigere Bind-Mount-Konstruktionen aus älteren Tutorials.
Schritt 6: Netzwerke verstehen und gezielt einsetzen
Compose legt für jedes Projekt automatisch ein eigenes Netzwerk an, in dem alle Services über ihren Dienstnamen erreichbar sind. Im Beispiel oben erreicht die App die Datenbank einfach über den Hostnamen db, nicht über localhost. Diese automatische DNS-Auflösung innerhalb des Projekt-Netzwerks ist einer der größten praktischen Vorteile von Compose gegenüber einzelnen, manuell verknüpften Containern, bei denen Hostnamen früher über das veraltete --link-Flag mühsam von Hand verdrahtet werden mussten. Willst du mehrere Netzwerke trennen, etwa ein öffentliches Frontend-Netz und ein internes Datenbank-Netz, definierst du sie explizit:
services:
app:
networks: [frontend, backend]
db:
networks: [backend]
networks:
frontend:
backend:
internal: true
Mit internal: true hat das Backend-Netz keinen Zugang zum Internet oder nach außen, was für Datenbank-Container aus Sicherheitssicht meist sinnvoll ist.
Schritt 7: Mehrere Umgebungen mit Override-Dateien abbilden
In der Praxis brauchst du selten exakt dieselbe Konfiguration für lokale Entwicklung, Staging und Produktion. Compose löst das über zusätzliche Dateien, die automatisch zusammengeführt werden. Lege etwa eine compose.override.yaml an:
services:
app:
volumes:
- .:/app
environment:
- NODE_ENV=development
command: npm run dev
Diese Datei wird automatisch zusätzlich zur compose.yaml geladen, wenn du nur docker compose up ausführst. Für Produktion rufst du stattdessen explizit eine andere Kombination auf:
docker compose -f compose.yaml -f compose.prod.yaml up -d
So bleibt die Basis-Konfiguration identisch, während sich nur die umgebungsspezifischen Teile ändern, etwa Replikation, Ressourcenlimits oder Logging.
Schritt 8: Projekte mit include aufteilen
Wird ein Compose-Projekt groß, lohnt sich eine Aufteilung in mehrere Dateien, die über das include-Feld eingebunden werden. Das ist besonders hilfreich, wenn mehrere Teams an unterschiedlichen Teilen desselben Gesamtsystems arbeiten:
include:
- path: ./services/auth/compose.yaml
- path: ./services/billing/compose.yaml
services:
gateway:
build: ./gateway
ports:
- "80:80"
Jede eingebundene Datei bleibt eigenständig wartbar, während die Haupt-Compose-Datei nur noch das Zusammenspiel beschreibt. Das ist ein deutlich saubererer Ansatz als eine einzige, hunderte Zeilen lange YAML-Datei.
Schritt 9: Logs lesen, in Container wechseln, Zustand prüfen
Für den Alltag brauchst du eine Handvoll Befehle, mit denen du siehst, was im laufenden Stack passiert, ohne jedes Mal das gesamte Setup neu zu starten oder zu raten, was gerade im Hintergrund abläuft:
docker compose ps
docker compose logs -f app
docker compose exec app sh
docker compose top
docker compose logs -f app folgt live den Logs eines einzelnen Dienstes, docker compose exec app sh öffnet eine Shell direkt im laufenden Container, praktisch zum schnellen Nachsehen, ob eine Datei existiert oder ein Prozess läuft. docker compose top zeigt dir die Prozesse aller Container auf einen Blick, nützlich wenn ein Container ungewöhnlich viel CPU verbraucht.
Schritt 10: Aufräumen und Volumes gezielt löschen
Ein häufiger Fehler ist, Container einfach mit Strg+C abzubrechen und zu glauben, damit sei alles beendet. Tatsächlich bleiben Netzwerke und teilweise Container im gestoppten Zustand liegen. Richtig beendest du einen Stack so:
docker compose down
Das entfernt Container und Netzwerke, lässt benannte Volumes aber bewusst bestehen, damit deine Datenbankinhalte nicht versehentlich verloren gehen. Willst du wirklich alles inklusive der Daten löschen, etwa für einen kompletten Neustart:
docker compose down -v
Das -v-Flag ist hier der entscheidende Unterschied, und genau deshalb auch eine der häufigsten Ursachen für versehentlich gelöschte Entwicklungsdatenbanken. Tippe diesen Befehl nie blind aus einer Bash-History heraus.
Schritt 11: Ressourcenlimits und Healthchecks härten
Ohne Limits kann ein einzelner Container im lokalen Setup theoretisch den gesamten verfügbaren Arbeitsspeicher oder alle CPU-Kerne beanspruchen. Für realistischere lokale Tests, die sich ähnlich wie eine Produktionsumgebung verhalten, lohnt es sich, Grenzen zu setzen:
services:
app:
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
memory: 256M
Kombiniert mit einem Healthcheck, der echte Anwendungsendpunkte prüft statt nur “Container läuft”, bekommst du frühzeitig mit, wenn ein Dienst zwar gestartet, aber nicht wirklich funktionsfähig ist.
Multi-Stage-Dockerfiles mit Compose kombinieren
Ein Punkt, der in vielen Compose-Tutorials zu kurz kommt, ist das Zusammenspiel mit Multi-Stage-Dockerfiles. Gerade bei Node.js- oder Go-Anwendungen willst du selten dasselbe Image für Entwicklung und Produktion verwenden: Während der Entwicklung brauchst du Compiler, Testwerkzeuge und alle Dev-Abhängigkeiten, im Produktionsimage sollen davon möglichst wenig Spuren übrig bleiben, schon allein um die Angriffsfläche klein zu halten. Ein Multi-Stage-Dockerfile trennt das in benannte Stufen:
# Dockerfile
FROM node:22-slim AS base
WORKDIR /app
COPY package*.json ./
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
FROM base AS production
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]
In der Compose-Datei wählst du über das target-Feld aus, welche Stufe gebaut werden soll:
services:
app:
build:
context: .
target: development
Für die compose.prod.yaml überschreibst du einfach das target auf production, der Rest der Konfiguration bleibt identisch. So testest du lokal mit allen Entwicklungswerkzeugen, deployst aber ein schlankes, produktionsoptimiertes Image, ohne zwei komplett getrennte Dockerfiles pflegen zu müssen.
Schritt 12: Vollständiges Beispielprojekt zusammenführen
Zum Abschluss fügen wir alle Bausteine zu einem vollständigen, funktionierenden Projekt zusammen. Die Ordnerstruktur sieht so aus:
compose-tutorial/
├── compose.yaml
├── compose.override.yaml
├── .env
├── .env.example
├── Dockerfile
└── src/
└── index.js
Die finale compose.yaml fasst Datenbank, Cache, App, Netzwerke, Healthchecks und Ressourcenlimits zusammen:
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@db:5432/appdb
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
networks: [backend]
deploy:
resources:
limits:
memory: 512M
db:
image: postgres:17
environment:
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=appdb
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 5s
timeout: 5s
retries: 5
networks: [backend]
cache:
image: redis:7
networks: [backend]
networks:
backend:
internal: true
volumes:
db-data:
Starte das Gesamtprojekt mit docker compose up --build -d und prüfe mit docker compose ps, ob alle drei Dienste als “healthy” markiert sind. Damit hast du ein reproduzierbares, geteiltes Entwicklungssetup, das jeder im Team mit einem einzigen Befehl starten kann.
Die 5 wichtigsten Stolperfallen bei Docker Compose
Auch erfahrene Entwickler tappen regelmäßig in dieselben Fallen. Hier die fünf häufigsten, zusammen mit der jeweiligen Lösung.
- Datenbank-Container verbindet sich über localhost statt über den Servicenamen. Innerhalb des Compose-Netzwerks heißt der Datenbankhost wie der Service in der YAML-Datei, also
db, nichtlocalhost.localhostwürde innerhalb des App-Containers auf den App-Container selbst zeigen, nicht auf den Datenbank-Container. Dieser Fehler ist besonders tückisch, weil der Code oft eins zu eins von einer nicht containerisierten lokalen Entwicklung übernommen wurde, wolocalhosttatsächlich korrekt war. - depends_on garantiert keine Anwendungsbereitschaft, nur den Containerstart. Ohne
condition: service_healthyund einen echten Healthcheck startet die App oft schneller als die Datenbank tatsächlich Verbindungen annehmen kann, was zu zufälligen Verbindungsfehlern beim ersten Start führt. Das Tückische daran: Auf einer schnellen Maschine fällt das Problem oft gar nicht auf, weil die Datenbank knapp schnell genug hochfährt, während es auf einem langsameren CI-Runner regelmäßig fehlschlägt. - Bind-Mounts überschreiben versehentlich den gesamten Containerinhalt. Wer
volumes: - .:/appohne weitere Überlegung einsetzt, überschreibt unter Umständen die im Image enthaltenennode_modules. Eine zusätzliche, benannte Volume-Definition fürnode_moduleslöst das Problem, indem sie verhindert, dass der leere Host-Ordner die bereits installierten Abhängigkeiten im Container verdeckt. - .env-Dateien landen im Git-Repository. Eine vergessene
.gitignore-Regel reicht, um Datenbankpasswörter öffentlich einsehbar zu machen. Prüfe das vor jedem ersten Commit eines neuen Projekts explizit, und falls ein Secret bereits in die Git-Historie gelangt ist, reicht ein nachträgliches Löschen der Datei nicht aus, das Passwort muss in diesem Fall ausgetauscht werden. - Alte version-Direktive und veraltete Compose-Dateisyntax vermischt. Tutorials aus den Jahren 2019 bis 2022 enthalten oft noch
version: "3"und Syntax, die mit der aktuellen Compose Specification zwar meist noch funktioniert, aber unnötige Warnungen erzeugt und bei neueren Feldern wiedevelop.watchzu Verwirrung führt, weil ältere Beispiele diese Felder schlicht noch nicht kennen.
Keiner dieser Fehler ist kompliziert zu beheben, sobald die Ursache klar ist. Das eigentliche Problem ist meist die Diagnose: Eine Fehlermeldung wie “connection refused” verrät auf den ersten Blick nicht, ob es an einem falschen Hostnamen, einem zu früh gestarteten Service oder einer falsch gesetzten Umgebungsvariable liegt. Wer die fünf genannten Muster kennt, spart sich in der Praxis die meiste Zeit bei der Fehlersuche.
Troubleshooting: Die 8 häufigsten Fehler und ihre Lösung
So gut ein Compose-Setup auch geplant ist, in der Praxis tauchen immer wieder dieselben Fehlermeldungen auf, meist beim ersten Start auf einer neuen Maschine oder nach einem größeren Update. Die folgende Tabelle fasst Fehlermeldungen zusammen, die in Foren und Issue-Trackern besonders oft auftauchen, zusammen mit der jeweiligen Ursache und Lösung. Die meisten dieser Probleme lassen sich in unter einer Minute beheben, sobald man weiß, wo man suchen muss.
| Fehlermeldung / Symptom | Ursache | Lösung |
|---|---|---|
| port is already allocated | Ein anderer Prozess oder Container belegt den Port bereits | Mit docker ps den blockierenden Container finden oder in der compose.yaml einen anderen Hostport wählen, z. B. “8081:80” |
| Cannot connect to the Docker daemon | Docker Desktop läuft nicht oder der Dienst ist abgestürzt | Docker Desktop neu starten, unter Linux sudo systemctl status docker prüfen |
| no configuration file provided: not found | Befehl wird im falschen Verzeichnis ausgeführt | Mit cd in den Ordner mit der compose.yaml wechseln oder -f /pfad/compose.yaml angeben |
| service “db” is unhealthy | Healthcheck schlägt dauerhaft fehl, meist falsche Zugangsdaten | Mit docker compose logs db die echte Fehlermeldung der Datenbank lesen |
| Änderungen am Code werden nicht übernommen | Kein Bind-Mount oder Watch-Modus aktiv, Container nutzt altes Image | docker compose watch nutzen oder nach Codeänderung docker compose up --build ausführen |
| no space left on device | Alte Images, Container und Volumes belegen Speicherplatz | docker system df zur Übersicht, dann gezielt mit docker system prune aufräumen |
| network has active endpoints | Ein Netzwerk kann nicht gelöscht werden, weil noch Container daran hängen | Zuerst docker compose down im betroffenen Projekt ausführen, danach erneut versuchen |
| Variable ist nicht gesetzt, Warnung beim Start | .env-Datei fehlt oder liegt im falschen Verzeichnis | Prüfen, ob .env im selben Ordner wie compose.yaml liegt, Name exakt “.env” ohne Zusatz |
Docker Compose in der CI/CD-Pipeline nutzen
Dieselbe compose.yaml, die du lokal zum Entwickeln verwendest, lässt sich fast unverändert in einer Continuous-Integration-Pipeline wiederverwenden. Der Vorteil: Integrationstests laufen gegen eine echte Datenbank und einen echten Cache, nicht gegen ein Mock-Objekt, das sich im Detail anders verhält als die echte Software. Ein typischer GitHub-Actions-Workflow sieht so aus:
name: Integrationstests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Stack starten
run: docker compose up -d --wait
- name: Tests ausführen
run: docker compose exec -T app npm test
- name: Logs bei Fehler ausgeben
if: failure()
run: docker compose logs
- name: Stack beenden
if: always()
run: docker compose down -v
Das Flag --wait bei docker compose up lässt GitHub Actions warten, bis alle Healthchecks erfolgreich durchlaufen sind, bevor der nächste Schritt startet. Das ist in CI-Umgebungen besonders wichtig, weil Runner oft langsamer hochfahren als die eigene Entwicklungsmaschine und ein zu früh ausgeführter Test sonst gegen eine noch nicht bereite Datenbank läuft. Der if: always()-Schritt am Ende sorgt dafür, dass der Stack auch nach einem fehlgeschlagenen Testlauf zuverlässig aufgeräumt wird und keine Ressourcen auf dem Runner verbleiben.
Sicherheit: Worauf du bei Compose-Setups achten solltest
Docker Compose wird meist für lokale Entwicklung eingesetzt, landet aber überraschend oft doch auf kleinen Produktionsservern, etwa bei Homelab-Projekten oder kleinen SaaS-Deployments. Dabei lohnt sich ein Blick auf aktuelle Sicherheitsmeldungen. Die Docker-Desktop-Release-Notes zu Version 4.94.0 vom 5. Oktober 2026 nennen ein aktualisiertes Kind-Cloud-Provider-Image, das die Schwachstellen CVE-2026-46595 und CVE-2026-39834 behebt. Das zeigt, dass auch das Docker-Ökosystem regelmäßig Patches braucht, und dass ein veraltetes Docker Desktop kein rein theoretisches Risiko ist.
Für produktive oder teilweise öffentlich erreichbare Compose-Stacks gelten dieselben Grundregeln wie bei jedem anderen System: Secrets nicht im Klartext in Umgebungsvariablen, sondern über einen dedizierten Secrets-Mechanismus verwalten, interne Netzwerke konsequent mit internal: true isolieren, und Container nicht standardmäßig als Root-Benutzer laufen lassen. Compose unterstützt dafür ein eigenes secrets-Feld, das ein Passwort als Datei statt als Umgebungsvariable in den Container einhängt, was das Risiko verringert, dass ein Secret versehentlich in Logs oder einer Prozessliste auftaucht:
services:
db:
image: postgres:17
secrets:
- db_password
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Wer zusätzlich den Zugriff auf den Docker-Host über SSH absichert, sollte sich unser Tutorial zum Einrichten von SSH-Keys ansehen, da ein kompromittierter SSH-Zugang in der Praxis oft der erste Schritt zu einem kompromittierten Docker-Host ist. Auch ein regelmäßiges docker compose pull vor jedem Neustart lohnt sich, damit Basis-Images wie postgres oder redis bekannte Sicherheitslücken zeitnah über neue Patch-Versionen schließen.
Fortgeschrittene Tipps für den produktiven Alltag
Sobald das Grundsetup läuft, lohnen sich einige Erweiterungen, die den Alltag merklich erleichtern.
Profiles erlauben es, Dienste optional zu machen. Ein Monitoring-Stack oder ein Admin-Tool muss nicht bei jedem docker compose up mitstarten:
services:
adminer:
image: adminer
profiles: ["debug"]
ports:
- "8081:8080"
Gestartet wird dieser optionale Dienst nur gezielt mit docker compose --profile debug up, im normalen Alltag bleibt er inaktiv und spart Ressourcen.
Für Teams, die KI-gestützte Entwicklungswerkzeuge einsetzen, lohnt sich ein Blick auf das neue, offizielle Go-SDK, das laut den Docker-Desktop-Release-Notes mit Compose v5 eingeführt wurde. Es erlaubt, Compose-Konfigurationen direkt aus eigenem Go-Code zu laden, zu validieren und zu verwalten, ohne die CLI aufzurufen, was für eigene Tooling- oder Automatisierungsprojekte interessant ist. Vorher war dafür meist der Umweg über das Parsen der YAML-Datei mit einer generischen YAML-Bibliothek nötig, inklusive aller Sonderfälle der Compose Specification, die ein eigens dafür gebautes SDK jetzt direkt abdeckt. Wer Compose-Setups automatisiert mit KI-Agenten verwalten lassen will, kann das mit Werkzeugen wie dem in unserem Tutorial zum Einrichten eines MCP-Servers beschriebenen Protokoll koppeln, etwa um Deployment-Schritte über ein standardisiertes Agent-Interface auszulösen.
Ein weiterer Tipp: Nutze docker compose config, um die final zusammengeführte Konfiguration aus allen Override-Dateien anzuzeigen, bevor du den Stack tatsächlich startest. Das hilft besonders, wenn mehrere Compose-Dateien kombiniert werden und nicht sofort klar ist, welcher Wert sich am Ende durchsetzt.
Wer lokale KI-Modelle in seinen Stack einbinden will, findet in Docker Desktop 4.94.0 eine interessante Erweiterung: Der sogenannte Docker Model Runner unterstützt laut den offiziellen Release-Notes inzwischen auch GPT-OSS-Modelle direkt. Praktisch lässt sich ein solches Modell als eigener Service in der Compose-Datei führen, ähnlich wie eine Datenbank, sodass eine Anwendung über einen lokalen Endpunkt darauf zugreifen kann, ohne Anfragen an einen externen Cloud-Dienst zu schicken. Für Teams, die aus Datenschutzgründen oder wegen der DSGVO sensible Daten nicht an externe KI-Anbieter senden wollen, ist das ein Ansatz, den man im Auge behalten sollte, auch wenn er für produktive Lasten aktuell noch deutlich mehr lokale Rechenleistung voraussetzt als ein klassischer Web- oder Datenbankdienst.
Bind Mounts und benannte Volumes im Detail
Ein Punkt, der Einsteigern besonders häufig Kopfzerbrechen bereitet, ist der Unterschied zwischen Bind Mounts und benannten Volumes. Beide tauchen im volumes-Block auf, verhalten sich aber grundlegend anders. Ein Bind Mount verweist auf einen konkreten Pfad auf deinem Host-Rechner, zum Beispiel deinen Projektordner, und spiegelt dessen Inhalt live in den Container. Das ist ideal während der Entwicklung, weil Codeänderungen sofort sichtbar sind, ohne dass du das Image neu bauen musst.
# Bind Mount: Pfad auf dem Host, beginnt mit . oder /
volumes:
- ./src:/app/src
# Benanntes Volume: von Docker selbst verwaltet
volumes:
- app-data:/app/data
Ein benanntes Volume dagegen wird komplett von Docker selbst verwaltet und liegt irgendwo im internen Speicherbereich von Docker, nicht an einem für dich direkt sichtbaren Pfad im Projektordner. Das ist der richtige Ansatz für Datenbankdaten, denn ein benanntes Volume überlebt sowohl Container-Neustarts als auch das Löschen und Neuanlegen eines Containers, solange du nicht explizit mit -v löschst. Bind Mounts dagegen eignen sich nicht für Datenbankdaten, weil unterschiedliche Dateisystemrechte zwischen Host und Container, besonders unter Windows und macOS, zu schwer nachvollziehbaren Performance-Problemen oder Berechtigungsfehlern führen können.
Eine häufige Faustregel in der Praxis: Code und Konfigurationsdateien über Bind Mounts einbinden, alles was der Container selbst an Daten erzeugt oder braucht (Datenbanken, Caches, Uploads) über benannte Volumes verwalten. Willst du wissen, welche Volumes aktuell auf deinem System existieren und wie viel Speicherplatz sie belegen, hilft dir docker volume ls gefolgt von docker system df -v für die detaillierte Größenübersicht.
Logging in Compose-Setups richtig konfigurieren
Standardmäßig schreibt Docker die Logs jedes Containers unbegrenzt in eine Datei auf dem Host. Bei einem lang laufenden Entwicklungscontainer, der viele Debug-Ausgaben produziert, kann das über Wochen hinweg mehrere Gigabyte an Speicherplatz verschlingen, ohne dass es auffällt, bis die Festplatte plötzlich voll ist. Für jeden Service lohnt sich daher eine explizite Begrenzung:
services:
app:
build: .
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Mit dieser Konfiguration rotiert Docker die Logdatei automatisch, sobald sie 10 Megabyte erreicht, und hält maximal drei solcher Dateien pro Container vor. Insgesamt kann ein Service damit also höchstens rund 30 Megabyte an Logs anhäufen, unabhängig davon, wie lange er läuft. Für produktivere Setups, die Logs zentral sammeln wollen, unterstützt Compose auch andere Logging-Treiber, etwa um Logs direkt an einen externen Log-Aggregator weiterzuleiten, statt sie nur lokal auf der Festplatte zu belassen.
Docker Compose im Vergleich zu Alternativen
Docker Compose ist nicht die einzige Option für lokale Mehrcontainer-Setups. Die folgende Tabelle ordnet die bekanntesten Alternativen ein.
| Werkzeug | Nutzungsanteil / Status 2026 | Besonderheit |
|---|---|---|
| Docker Compose | 59,1% laut Stack Overflow Developer Survey 2026 (gemeinsam mit Docker) | Größtes Ökosystem, Standard-Dateiformat, in Docker Desktop integriert |
| Podman Compose / Podman | 13% laut derselben Erhebung | Rootless-Betrieb möglich, kompatibel zu vielen Compose-Dateien |
| Kubernetes (lokal, z. B. Minikube) | 26% laut derselben Erhebung | Für Produktionsähnlichkeit, deutlich höherer Konfigurationsaufwand |
Für die meisten lokalen Entwicklungsszenarien bleibt Docker Compose die pragmatischste Wahl: Die Einstiegshürde ist niedrig, die Dateien sind gut lesbar, und die Community-Ressourcen sind entsprechend der hohen Nutzungszahlen aus der Stack-Overflow-Erhebung breit vorhanden. Kubernetes lohnt sich meist erst, wenn die lokale Umgebung möglichst exakt die Produktionsarchitektur spiegeln soll, etwa bei komplexen Microservice-Landschaften mit eigenem Scheduling.
Podman verdient an dieser Stelle einen genaueren Blick, weil es als einziger Kandidat Compose-Dateien weitgehend direkt versteht. Wer rootless arbeiten muss, etwa auf gehärteten Firmenrechnern ohne root-Rechte, findet in Podman eine Alternative, die ohne durchgehend laufenden Hintergrund-Daemon funktioniert. Der Umstieg bedeutet in vielen Fällen nur, docker durch podman in den eigenen Skripten zu ersetzen, die compose.yaml selbst bleibt meist unverändert nutzbar. Wer sich für den vollständigen Preisvergleich der Docker-Desktop-Tarife interessiert, findet die aktuelle Übersicht auf der offiziellen Docker-Pricing-Seite.
Typische Ausgabe beim erfolgreichen Start
Damit du einschätzen kannst, ob dein Setup korrekt läuft, hier die erwartete Ausgabe nach docker compose up -d für das vollständige Beispielprojekt aus Schritt 12:
[+] Running 4/4
✔ Network compose-tutorial_backend Created
✔ Container compose-tutorial-db-1 Healthy
✔ Container compose-tutorial-cache-1 Started
✔ Container compose-tutorial-app-1 Started
Und die Ausgabe von docker compose ps direkt danach:
NAME IMAGE STATUS
compose-tutorial-app-1 compose-tutorial-app Up 12 seconds
compose-tutorial-cache-1 redis:7 Up 12 seconds
compose-tutorial-db-1 postgres:17 Up 12 seconds (healthy)
Steht bei der Datenbank nach einigen Sekunden immer noch nicht “(healthy)”, lohnt ein Blick in die Logs mit docker compose logs db, meist liegt es an falschen Zugangsdaten zwischen App- und Datenbank-Umgebungsvariablen.
Wann sich der Umstieg auf Docker Compose wirklich lohnt
Nicht jedes Projekt braucht sofort einen vollständigen Compose-Stack. Für ein einzelnes statisches Skript ist der Aufwand meist unnötig. Sobald aber mindestens zwei zusammenspielende Dienste im Spiel sind, etwa eine Anwendung und eine Datenbank, zahlt sich die Investition schnell aus. Besonders deutlich wird der Effekt beim Onboarding neuer Teammitglieder: Statt einer mehrseitigen Setup-Anleitung mit zehn einzelnen Installationsschritten reicht ein Klonen des Repositories und ein einziger Befehl, um exakt denselben Zustand wie bei allen anderen im Team herzustellen.
Auch für Continuous-Integration-Pipelines ist das Muster etabliert. Wer bereits Workflows mit GitHub Actions betreibt, kann dieselbe compose.yaml, die lokal genutzt wird, eins zu eins im CI-Job verwenden, um Integrationstests gegen eine echte Datenbank statt gegen eine gemockte Version laufen zu lassen. Das verringert die Lücke zwischen Testumgebung und echtem Produktionsverhalten erheblich. Wer seine Projekte bereits mit einem KI-gestützten Terminal-Assistenten oder einer Agenten-IDE wie Claude Code entwickelt, profitiert zusätzlich: Die Compose-Datei gibt dem Assistenten eine klare, maschinenlesbare Beschreibung der Projektarchitektur, was bessere Vorschläge für Debugging und neue Services ermöglicht.
Ein weiterer, oft unterschätzter Vorteil zeigt sich bei Open-Source-Projekten mit vielen externen Mitwirkenden. Eine klare compose.yaml im Repository-Root senkt die Einstiegshürde für neue Beitragende erheblich, weil niemand mehr erst eine mehrseitige Setup-Anleitung durcharbeiten muss, bevor der erste Pull Request möglich wird. Gerade bei Projekten, die mehrere externe Abhängigkeiten wie Datenbanken oder Message-Queues benötigen, ist das ein messbarer Unterschied in der Zahl eingehender Beiträge.
Häufig gestellte Fragen
Ist Docker Compose kostenlos nutzbar?
Ja. Docker Compose selbst ist Open Source und kostenlos. Docker Desktop, in dem Compose standardmäßig enthalten ist, ist für private Nutzung, Bildungszwecke, nicht-kommerzielle Open-Source-Projekte sowie für kleine Unternehmen mit weniger als 250 Beschäftigten und weniger als 10 Millionen US-Dollar Jahresumsatz kostenlos. Größere Unternehmen benötigen einen kostenpflichtigen Pro-, Team- oder Business-Tarif.
Brauche ich Docker Desktop oder reicht die Docker Engine?
Unter Linux reicht die Docker Engine mit installiertem Compose-Plugin völlig aus, eine grafische Oberfläche ist nicht notwendig. Unter Windows und macOS ist Docker Desktop der übliche und am besten unterstützte Weg, Docker Engine und Compose gemeinsam zu betreiben.
Was ist der Unterschied zwischen docker-compose und docker compose?
Die Schreibweise mit Bindestrich stammt aus Compose v1, einem separat installierten Python-Tool, das seit mehreren Jahren nicht mehr weiterentwickelt wird. Die Schreibweise mit Leerzeichen ist ein in die Docker-CLI integriertes Plugin und der aktuelle Standard, den auch dieses Tutorial durchgehend verwendet.
Gehen meine Daten verloren, wenn ich den Container neu starte?
Nein, solange die Daten in einem benannten Volume liegen, wie im Beispiel mit db-data:/var/lib/postgresql/data. Ein einfacher Neustart oder docker compose down ohne das -v-Flag lässt benannte Volumes unangetastet. Erst docker compose down -v löscht sie tatsächlich.
Kann ich Docker Compose für Produktionsumgebungen verwenden?
Für kleine bis mittlere Setups, etwa einzelne Server oder Homelab-Projekte, wird Compose in der Praxis häufig produktiv eingesetzt. Für große, verteilte Systeme mit automatischer Skalierung über mehrere Maschinen ist Kubernetes meist die passendere Wahl, da Compose kein eingebautes Multi-Host-Scheduling bietet. Ein gängiger Mittelweg ist, Compose für Staging- und kleinere Produktionsumgebungen zu nutzen und erst bei nachgewiesenem Skalierungsbedarf auf eine komplexere Orchestrierungslösung umzusteigen.
Wie aktualisiere ich Docker Compose auf die neueste Version?
Ist Compose über Docker Desktop installiert, reicht ein Update von Docker Desktop selbst, aktuell auf Version 4.94.0. Unter Linux mit separat installiertem Compose-Plugin aktualisierst du es über den Paketmanager deiner Distribution oder lädst das aktuelle Release direkt von der offiziellen GitHub-Releases-Seite.
Warum startet mein Container sofort wieder neu (Restart-Loop)?
Das deutet meist auf einen Absturz der Anwendung direkt beim Start hin, oft wegen fehlender Umgebungsvariablen oder einer noch nicht erreichbaren Datenbank. Mit docker compose logs DIENSTNAME siehst du die letzte Fehlermeldung vor dem Absturz, das ist fast immer der schnellste Weg zur Ursache.
Unterstützt Docker Compose auch ARM-Prozessoren wie Apple Silicon?
Ja, sowohl Docker Desktop als auch die meisten offiziellen Images auf Docker Hub unterstützen heute Multi-Architektur-Builds für ARM64 und AMD64. Bei selbst gebauten Images solltest du bei Bedarf gezielt docker buildx für Multi-Plattform-Builds nutzen, falls dein Team auf unterschiedlicher Hardware arbeitet.




