FlowingDev

Docker Compose, erklärt: die Universalfernbedienung für deine Container

Lerne, wie Docker Compose eine YAML-Datei nutzt, um Multi-Container-Anwendungen zu verwalten und komplexe `docker run`-Befehle in eine einzige, teilbare Blaupause verwandelt.

Tool ausprobieren: Docker Run zu Compose

In einem Satz

Docker Compose ist ein Tool, mit dem du Multi-Container-Anwendungen über eine einzige, menschenlesbare YAML-Datei definieren und ausführen kannst, anstatt einen Haufen langer, klobiger docker run-Befehle zu verwenden.

Das Problem, das es löst

Am Anfang war docker run. Und es war... okay. Um einen Container auszuführen, ist es in Ordnung. Du tippst deinen Befehl ein, vielleicht mit einem Port-Mapping und einem Volume, und zack, hast du einen laufenden Service.

Aber was passiert, wenn deine App nicht nur aus einem Service besteht? Was, wenn du ein Web-Frontend, eine Backend-API, eine PostgreSQL-Datenbank und einen Redis-Cache hast? Plötzlich hast du es nicht nur mit einem Befehl zu tun. Du jonglierst mit vier davon.

Du musst manuell ein Netzwerk erstellen, damit sie miteinander kommunizieren können. Du musst sicherstellen, dass du sie nicht versehentlich alle auf denselben Host-Port mappst. Du musst dich an den exakten, sehr langen docker run-Befehl für jeden Service erinnern, mit all seinen Umgebungsvariablen und Volume-Mounts. Wenn du dieses Setup mit einem neuen Teamkollegen teilen willst, schreibst du eine riesige README.md-Datei, die im Grunde ein Gebet ist, dass sie alles korrekt kopieren und einfügen. Es ist ein Rezept für Chaos, Tippfehler und das klassische "Also, auf meinem Rechner läuft's!"-Dilemma.

Dieses Chaos sollte Docker Compose, das aus einem von Docker 2014 übernommenen Projekt namens Fig hervorging, bändigen. Es geht darum, vom Management einzelner Container-"Haustiere" zur Orchestrierung einer ganzen Anwendungs-"Herde" überzugehen. Anstatt imperative Befehle zu erteilen (führe dies aus, dann das, verknüpfe sie so...), schreibst du eine einzige deklarative Datei (so sieht mein gesamter Anwendungs-Stack aus) und überlässt es Compose, die unschönen Details der Umsetzung zu regeln.

Wie es unter der Haube funktioniert

Im Kern ist Docker Compose ein Übersetzer. Es übersetzt eine beschreibende YAML-Datei in eine Reihe von API-Aufrufen an die Docker Engine. Es ist das "Zeigen, nicht erzählen" des Container-Managements.

Von docker run-Flags zu YAML-Keys

Nehmen wir an, du möchtest eine PostgreSQL-Datenbank ausführen. Der docker run-Befehl könnte wie eine Zeile Buchstabensalat aus einem Hackerfilm aussehen:

docker run --name my-postgres-db \
  -e POSTGRES_PASSWORD=supersecret \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  --restart unless-stopped \
  -d postgres:14

Dieser Befehl hat einen Namen, eine Umgebungsvariable für das Passwort, ein Port-Mapping, ein Named Volume zur Persistierung von Daten, eine Neustart-Richtlinie und er läuft im Hintergrund (-d).

Schauen wir uns nun das Docker-Compose-Äquivalent in einer docker-compose.yml-Datei an:

version: '3.8' # Gibt die Version der Compose-Datei an

services:
  db: # Dies ist ein freundlicher Name für unseren Service
    image: postgres:14
    container_name: my-postgres-db
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: supersecret
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata: # Hier deklarieren wir das Named Volume

Siehst du die Parallele? Die Kommandozeilen-Flags sind zu sauberen, organisierten YAML-Schlüsseln geworden. Das ist nicht nur schöner; es ist eine versionierbare, teilbare und weitaus weniger fehleranfällige Definition deines Services.

docker run-Flag docker-compose.yml-Key Was es bewirkt
postgres:14 (das Image) image: postgres:14 Gibt das zu verwendende Container-Image an.
--name my-postgres-db container_name: my-postgres-db Gibt dem Container einen spezifischen Namen.
-p 5432:5432 ports: ["5432:5432"] Mappt einen Port vom Host zum Container.
-v pgdata:/var/lib/... volumes: [pgdata:/var/lib/...] Mountet ein Volume für persistente Daten.
-e VAR=value environment: { VAR: value } Setzt Umgebungsvariablen innerhalb des Containers.
--restart unless-stopped restart: unless-stopped Definiert die Neustart-Richtlinie des Containers.

Die Magie des Networkings

Hier glänzt Compose so richtig. Wenn du einen Web-App-Service zu derselben YAML-Datei hinzufügen würdest, etwa so:

services:
  db:
    # ... (unsere Postgres-Konfiguration von oben)
  
  api:
    image: my-cool-api:latest
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://user:supersecret@db:5432/mydatabase
    depends_on:
      - db

Wenn du docker-compose up ausführst, macht Compose etwas Geniales: Es erstellt ein dediziertes virtuelles Netzwerk für deine Anwendung. Sowohl der db- als auch der api-Container werden an dieses Netzwerk angehängt.

Jetzt kann der api-Service eine Verbindung zur Datenbank über den Hostnamen db herstellen – denselben Namen, den wir dem Service in der YAML-Datei gegeben haben! Kein Nachschlagen von IP-Adressen oder anfällige --link-Flags mehr. Compose kümmert sich um die gesamte DNS-Auflösung innerhalb seines privaten Netzwerks. So kann dein Web-App-Container deinem Datenbank-Container sicher und zuverlässig süße Nichtigkeiten (oder, du weißt schon, SQL-Queries) zuflüstern.

Der Compose-Lebenszyklus

Der docker-compose-Befehl ist dein Kontrollpanel für den gesamten Anwendungs-Stack.

  • docker-compose up: Das ist der Hauptbefehl. Er liest deine docker-compose.yml, überprüft den Zustand deines Systems und tut alles Notwendige, um ihn an die Datei anzupassen. Er erstellt Netzwerke, baut Images (wenn du eine build-Anweisung angegeben hast), erstellt Volumes und startet alle deine Service-Container. Wenn du ihn erneut ausführst, prüft er intelligent auf Änderungen und erstellt nur die Container neu, die aktualisiert werden müssen.
  • docker-compose down: Das freundliche Gegenteil von up. Es stoppt und entfernt alle Container und standardmäßig auch das von ihm erstellte Netzwerk. Es ist der "Räum meinen Mist auf"-Button. Für einen wirklich sauberen Tisch entfernt docker-compose down -v auch die Named Volumes.
  • docker-compose ps: Zeigt dir den Status aller von deiner Compose-Datei verwalteten Container an.
  • docker-compose logs -f: Verfolgt die Logs all deiner Services in einem einzigen praktischen Stream, farblich nach Service kodiert. Unbezahlbar für das Debugging.

Geschichten aus der Praxis

Die "Auf meinem Rechner geht's"-Saga

Devon hat gerade ein neues Feature für das Analyse-Dashboard seiner Firma fertiggestellt, das ein Node.js-Backend und eine MongoDB-Datenbank verwendet. Auf seinem Laptop funktioniert alles perfekt. Er pusht den Code und sagt einem neuen Mitarbeiter, Sam, er solle ihn pullen und ausführen. Devons README enthält zwei riesige docker run-Befehle. Sam, menschlich wie er ist, macht einen Tippfehler im Pfad des Datenbank-Volumes. Die App startet, kann aber nichts speichern. Sam verbringt drei Stunden mit dem Debuggen des App-Codes, weil er denkt, der Fehler liege dort, während Devon darauf besteht, dass alles in Ordnung ist. Schließlich teilen sie ihren Bildschirm und entdecken den Tippfehler. Die Frustration ist hoch.

Die Lektion: Hätten sie eine docker-compose.yml verwendet, wäre das Onboarding ein einziger docker-compose up-Befehl gewesen. Die gesamte Umgebung – Ports, Volumes, Umgebungsvariablen – wäre identisch gewesen, was eine ganze Klasse von Einrichtungsfehlern eliminiert und einen halben Tag Debugging in einen 30-Sekunden-Start verwandelt hätte.

Der CI/CD-Pipeline-Albtraum

Ein DevOps-Team baute eine Continuous-Integration-Pipeline, um ihre Microservices-App zu testen. Die App hatte einen Webserver, ein API-Gateway, einen User-Service und einen Redis-Cache. Ihr Jenkinsfile war ein furchterregendes Skript aus Shell-Befehlen, mit vier separaten docker run-Aufrufen und einem Haufen sleep 30-Befehlen in der Hoffnung, dass die Datenbank bereit wäre, bevor die API versuchte, sich mit ihr zu verbinden. Die Tests waren wackelig; manchmal liefen sie durch, manchmal schlugen sie fehl, weil der sleep nicht lang genug war.

Die Lektion: Sie schrieben die Pipeline um, um Docker Compose zu verwenden. Das Jenkinsfile wurde zu docker-compose -f docker-compose.ci.yml up -d vereinfacht. Sie nutzten depends_on mit condition: service_healthy, um sicherzustellen, dass die API erst startete, nachdem die Datenbank ihren internen Health-Check bestanden hatte. Die wackeligen Tests verschwanden über Nacht. Compose bot eine zuverlässige, deklarative Methode, um jedes Mal die exakt benötigte Testumgebung zu definieren und zu starten.

Der vergessene Sidecar

Eine Entwicklerin, Maria, deployte ihre Python-Hauptanwendung in einem Docker-Container. Ein paar Wochen später bat ihr Manager sie, detailliertes Performance-Monitoring hinzuzufügen. Der beste Weg war, einen Prometheus-Exporter als "Sidecar" laufen zu lassen – ein zweiter Container, der neben der Haupt-App lebt und sich deren Netzwerk teilt, um Metriken zu scrapen. Maria richtete ihn mit einem weiteren docker run-Befehl ein. Aber jetzt musste sie daran denken, zwei Container zu starten, zu stoppen und neu zu starten, jedes Mal, wenn sie eine Änderung vornahm. Eines Tages startete sie die App neu, vergaß aber den Sidecar, und sie verloren einen ganzen Tag an Metriken.

Die Lektion: Maria hat ihr Setup in eine docker-compose.yml umgestaltet. Sie definierte ihre Python-App als einen Service und den Prometheus-Exporter als einen zweiten. Jetzt erweckte docker-compose up beide zum Leben, und docker-compose down schickte sie gemeinsam in den Ruhestand. Sie wurden als eine einzige logische Einheit verwaltet, so wie es sein sollte. Compose macht Muster wie Sidecars trivial zu verwalten.

Häufige Fehler und Fallen

  • depends_on wartet auf Bereitschaft – ein Trugschluss. Standardmäßig wartet depends_on nur darauf, dass der abhängige Container startet, nicht darauf, dass die Anwendung darin vollständig initialisiert und bereit ist, Verbindungen anzunehmen. Deine API könnte starten, bevor deine Datenbank bereit ist, abgefragt zu werden, was zu einem Absturz führt. Die echte Lösung ist, die healthcheck-Anweisung in der Definition deines Datenbank-Services zu verwenden und dann die Langform der depends_on-Syntax mit condition: service_healthy zu nutzen.
  • Geheimnisse in Git einchecken. Es ist unglaublich verlockend, einfach POSTGRES_PASSWORD: mein-geheimes-passwort direkt in die docker-compose.yml zu schreiben. Tu es nicht. Jeder, der Zugriff auf dein Repository hat, hat jetzt dein Datenbankpasswort. Der korrekte Weg ist die Verwendung einer .env-Datei (die du zu .gitignore hinzufügst) und die Referenzierung von Variablen wie ${POSTGRES_PASSWORD} in deiner Compose-Datei.
  • Bind-Mounts und Named Volumes verwechseln. Ein Pfad, der mit . oder / beginnt (z.B. ./my-app:/app), ist ein Bind-Mount, der ein Verzeichnis von deiner Host-Maschine mappt. Das ist super für die Entwicklung, damit Code-Änderungen sofort sichtbar sind. Ein Pfad, der nur mit einem Namen beginnt (z.B. db-data:/var/lib/mysql), ist ein von Docker verwaltetes Named Volume. Das ist besser, um Daten wie die einer Datenbank zu persistieren, da es plattformunabhängig ist und von der Docker-Engine verwaltet wird. Die falsche Wahl kann zu Berechtigungsproblemen oder Daten führen, die nicht so portabel sind, wie du denkst.
  • Einsatz in großen Produktionsumgebungen. Docker Compose ist fantastisch für Entwicklung, Tests und sogar für kleine Produktions-Deployments auf einem einzigen Host. Aber es ist KEIN Ersatz für einen ausgewachsenen Container-Orchestrator wie Kubernetes oder Docker Swarm. Es hat kein eingebautes Konzept für Multi-Node-Cluster, Selbstheilung, Load Balancing über Nodes hinweg oder ausgeklügelte Rolling Updates. Kenne seine Grenzen.

Warum du es auf dem Schirm haben solltest

Du solltest an Docker Compose denken, sobald deine Anwendung aus mehr als einem beweglichen Teil besteht.

  • Du baust eine Web-App mit einer separaten Datenbank? Verwende Compose.
  • Du musst deine App, einen Cache und einen Hintergrund-Worker ausführen? Verwende Compose.
  • Du willst deinen Teamkollegen einen Ein-Befehl-Weg geben, um den gesamten Entwicklungs-Stack auszuführen? Verwende Compose.
  • Du richtest eine CI-Pipeline ein, die eine temporäre Umgebung für Tests hochfahren muss? Verwende Compose.

Es ist das standardmäßige, branchenübliche Werkzeug für die lokale containerbasierte Entwicklung. Es zu lernen ist der natürliche nächste Schritt nach docker run, und es ist das Einstiegstor zum Verständnis der komplexeren Welt der Anwendungs-Orchestrierung. Es schließt die Lücke zwischen dem Ausführen eines einzelnen Containers und dem Betreiben eines vollwertigen, verteilten Systems.

Geh tiefer

  • Docker Docs: Compose file version 3 reference - Die offizielle Spezifikation und Quelle der Wahrheit für alle verfügbaren Schlüssel und Syntax.
  • Docker Docs: Overview of Docker Compose - Eine übergeordnete Einführung von den Machern selbst.
  • YAML Spec - Da alle docker-compose.yml-Dateien YAML sind, kann das Verständnis der Syntax (Maps, Listen, Anker) sehr hilfreich sein.
  • Awesome Docker - Eine kuratierte Liste von Docker-Ressourcen, von denen viele Tutorials und Beispiel-docker-compose.yml-Dateien für verschiedene Stacks enthalten.

Theorie erledigt. Zeit, loszulegen — 100 % in deinem Browser.

Tool ausprobieren: Docker Run zu Compose