Das Problem, das Compose löst
Eine echte Anwendung ist selten nur ein Container. Eine Web-API braucht eine Datenbank, die Datenbank braucht ein Volume, damit ihre Daten einen Neustart überstehen, und dann kommen noch ein Redis-Cache und ein Hintergrund-Worker dazu. Das sind vier docker run-Befehle, jeder mit eigenen Flags für Ports, Volumes und Umgebungsvariablen, dazu ein gemeinsames Netzwerk. In der richtigen Reihenfolge. Jedes Mal, wenn Sie sich an die Arbeit setzen.
Docker Compose ersetzt diese Befehle durch eine Datei und einen Befehl. Sie beschreiben die Container und ihre Verbindungen in einer Datei namens compose.yaml, und docker compose up startet sie alle. Wenn Sie mit Images und docker run noch nicht gearbeitet haben, lesen Sie zuerst was Docker ist und wie Container funktionieren.
Was die Compose-Datei beschreibt
Eine Compose-Datei hat eine Handvoll Schlüssel auf oberster Ebene. Einen davon verwenden Sie immer: services – jeder Service ist ein Container, den Compose ausführt.
| |
Zwei Container. api wird aus dem Dockerfile im aktuellen Verzeichnis gebaut und gibt Port 8000 frei. db führt das offizielle Image postgres:18 aus und legt seine Daten in einem benannten Volume ab, sodass die Daten erhalten bleiben, wenn der Container neu erstellt wird.
Zwei Details erledigen hier die eigentliche Arbeit:
apierreichtdbüber den Hostnamendb. Compose legt jeden Service in ein gemeinsames Netzwerk und registriert jeden Servicenamen als DNS-Namen. Keine IP-Adressen, kein altes--link.DATABASE_URLzeigt aufdb:5432, und Compose löst den Namen auf.depends_onmitcondition: service_healthyhältapizurück, bis Postgres wirklich antwortet. Ein einfachesdepends_onwartet nur darauf, dass der Container startet, nicht darauf, dass der Datenbankprozess darin Verbindungen annimmt – dieser Unterschied ist der häufigste Grund für ein “connection refused” beim ersten Start.
Die drei Schlüssel auf oberster Ebene: services, volumes, networks
Drei Schlüssel auf oberster Ebene entsprechen Docker-Konzepten, die Sie schon kennen:
| Schlüssel | Was er definiert | Von Hand mit |
|---|---|---|
services | die auszuführenden Container | docker run |
volumes | benannte Volumes für Daten, die bestehen bleiben müssen | docker volume create |
networks | Netzwerke zwischen Services | docker network create |
networks deklarieren Sie selten. Compose erstellt ein Netzwerk pro Projekt und hängt jeden Service daran an. Deshalb brauchte das Postgres-Beispiel keine Netzwerkkonfiguration. Deklarieren Sie Netzwerke nur dann explizit, wenn Sie verhindern wollen, dass Servicegruppen einander erreichen:
| |
Die Befehle, die Sie wirklich verwenden
Diese Befehle führen Sie in dem Verzeichnis aus, in dem compose.yaml liegt.
| |
up und down sind das Paar, das Sie am häufigsten tippen. up können Sie gefahrlos erneut aufrufen: Nach einer Änderung an der Datei erstellt es nur die Services neu, deren Konfiguration sich geändert hat, und lässt den Rest in Ruhe.
Was docker compose up beim ersten Start ausgibt
Führen Sie das Postgres-Beispiel von oben aus, und Sie bekommen ungefähr das:
| |
Jedem Namen stellt Compose den Projektnamen voran (standardmäßig den des Verzeichnisses) und hängt eine Nummer an, weil Compose auch mehrere Repliken eines Service ausführen kann. app-db-1 meldet Healthy, bevor app-api-1 startet – hier wirkt condition: service_healthy.
Wie Sie Passwörter aus der Compose-Datei heraushalten
Compose liest eine Datei namens .env im Projektverzeichnis und ersetzt ${VAR}-Referenzen in der Compose-Datei:
| |
| |
Halten Sie .env aus der Versionsverwaltung heraus und committen Sie stattdessen eine .env.example mit leeren oder Dummy-Werten. In einer echten Deployment-Umgebung verwenden Sie für alles wirklich Sensible Docker Secrets statt Umgebungsvariablen.
Optionale Services mit Profiles
Nicht jeder Service muss jedes Mal starten. Ein profiles-Eintrag hält einen Service inaktiv, bis Sie ihn anfordern:
| |
docker compose up startet api und db und ignoriert seed komplett. docker compose --profile tools run --rm seed führt den Seeder aus, wenn Sie ihn anfordern. Verwenden Sie Profiles für alles Einmalige: Seeder, Migrationen, eine Debug-Shell, einen Lastgenerator, den Sie nur für einen Test brauchen.
Compose v2 vs das alte docker-compose
Wenn eine Anleitung Ihnen sagt, Sie sollen docker-compose mit Bindestrich ausführen, stammt sie von vor 2023. Das war v1, in Python geschrieben, inzwischen am Ende ihres Lebenszyklus. Das aktuelle Werkzeug ist docker compose als Unterbefehl der Docker-CLI; es ist in Docker Desktop und im Compose-Plugin-Paket für Docker Engine enthalten.
Ältere Dateien haben oft noch diese Zeile am Anfang:
| |
Der Schlüssel version hat in Compose v2 keine Wirkung. Compose validiert gegen die aktuelle Compose Specification und warnt, wenn der Schlüssel vorhanden ist. Entfernen Sie ihn, und die Warnung verschwindet.
Auch der Standard-Dateiname hat sich geändert: compose.yaml ist der aktuelle, docker-compose.yml funktioniert weiterhin, und Compose sucht nach beiden.
Live-Reload während der Entwicklung
docker compose watch (Compose 2.22 und neuer) aktualisiert Container, während Sie am Code arbeiten. Fügen Sie dem Service einen develop-Block hinzu:
| |
sync kopiert geänderte Dateien direkt in den laufenden Container, sodass eine Code-Änderung ohne Rebuild sichtbar wird. rebuild löst einen vollständigen Image-Rebuild aus, wenn sich eine Datei ändert, die den Build betrifft, etwa eine Lock-Datei für Abhängigkeiten. Starten Sie es mit docker compose watch oder hängen Sie --watch an up an.
Wann Compose das falsche Werkzeug ist
Compose führt Container auf einer Maschine aus. Das ist die Grenze, und das meiste, was Leute in Compose vermissen, liegt auf der anderen Seite.
Es passt zu lokaler Entwicklung, automatisierten Tests in der CI und kleinen Deployments auf einem einzelnen Host. Es kann keine Container über mehrere Server verteilen, keinen ersetzen, wenn ein Knoten ausfällt, keine schrittweisen Rollouts an Healthchecks knüpfen und nicht automatisch skalieren.
Das sind Aufgaben eines Orchestrators: Kubernetes, oder Docker Swarm, wenn Sie etwas Leichteres wollen. Die beiden stehen nicht in Konkurrenz. Eine Compose-Datei ist oft der Entwurf, der später zu einem Satz Kubernetes-Manifeste wird, und viele Teams nutzen Compose lokal weiter, lange nachdem die Produktion auf einen Cluster umgezogen ist.
Podman-Nutzer haben ebenfalls einen kompatiblen Weg: podman compose und podman-compose lesen dasselbe Dateiformat, mit den Einschränkungen, die Docker vs Podman behandelt.
Häufige Fehler
- Secrets, die in
compose.yamleingecheckt sind. Die Datei landet in Git. Verwenden Sie.env(in gitignore) oder Docker Secrets. - Sich auf
depends_onverlassen, um auf die Bereitschaft eines Service zu warten. Für sich allein wartet es darauf, dass der Container startet, nicht darauf, dass der Service Verbindungen annimmt. Kombinieren Sie es mit einemhealthcheckundcondition: service_healthy. container_namesetzen. Das verhindert, dass Sie mehr als eine Kopie des Projekts ausführen, unddocker compose up --scalefunktioniert nicht mehr. Lassen Sie Compose die Container benennen.- Einen Bind-Mount über ein Verzeichnis mit installierten Abhängigkeiten legen. Den Projektordner in einen Node- oder Python-Container zu mounten, kann das
node_modulesoder virtualenv verdecken, das während des Builds erstellt wurde. Mounten Sie Unterordner des Quellcodes, oder legen Sie ein anonymes Volume auf den Abhängigkeitspfad.
Wie Sie eine bestehende App auf Compose umstellen
Nehmen Sie die App, die Sie heute mit einem Skript voller docker run-Zeilen starten, und überführen Sie sie Service für Service in eine compose.yaml. Starten Sie sie, lesen Sie die Logs, beheben Sie, was bricht, fügen Sie den nächsten Service hinzu. Geben Sie allem, wovon andere Services abhängen, einen healthcheck – das ist der Schritt, den man gern überspringt, und genau der, der das “connection refused” beim Kaltstart verhindert.
Sie wissen, dass es geklappt hat, wenn docker compose down -v && docker compose up die ganze Umgebung von Grund auf neu aufbaut und die App danach jedes Mal gleich läuft.