Überarbeitet am 8. September 2026.
Docker Compose gehört zu den Werkzeugen, die ich auf meinem Server ständig benutze. Statt lange docker run-Befehle mit Ports, Volumes und Umgebungsvariablen zusammenzubauen, steht die komplette Konfiguration einer Anwendung in einer YAML-Datei.
Meine ursprüngliche Erklärung war inzwischen deutlich veraltet. Sie verwendete noch docker-compose mit Bindestrich, alte Compose-Dateiversionen und einige Optionen, die man heute anders löst. Deshalb fangen wir noch einmal sauber von vorne an.
Wenn Docker und das Compose-Plugin noch fehlen, geht es zuerst hier entlang: Docker und Docker Compose unter Debian und Ubuntu installieren.
Was ist Docker Compose?
Compose beschreibt eine oder mehrere zusammengehörige Docker-Dienste als Projekt. Ein typisches Beispiel wäre WordPress mit einem Webcontainer und einer MariaDB-Datenbank. Beide Container werden gemeinsam gestartet, bekommen ein eigenes Netzwerk und können persistente Volumes verwenden.
Der heutige Befehl lautet:
docker compose up -d
Das alte Kommando docker-compose stammt aus Compose v1. Aktuelle Compose-Versionen werden als Docker-Plugin über docker compose aufgerufen.
Wie heißt die Compose-Datei?
Heute ist compose.yaml der bevorzugte Standardname. compose.yml funktioniert ebenfalls. Die alten Namen docker-compose.yml und docker-compose.yaml unterstützt Docker aus Kompatibilitätsgründen weiterhin.
Und nein: Die Datei muss nicht zwingend so heißen. Mit -f kann man eine beliebige Datei angeben:
docker compose -f compose.prod.yaml up -d
Eine einfache compose.yaml
services:
web:
image: nginx:alpine
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
Mehr braucht ein kleines Compose-Projekt tatsächlich nicht. Die früher übliche Zeile version: '3' fehlt bewusst: Docker kennzeichnet das Top-Level-Feld version inzwischen als obsolet und verwendet unabhängig davon die aktuelle Compose Specification.
services: die eigentlichen Container
Unter services stehen die einzelnen Bestandteile einer Anwendung. In unserem Beispiel heißt der Dienst web.
Bei einer größeren Anwendung könnte das so aussehen:
services:
web:
image: nginx:alpine
db:
image: mariadb:11
Die Namen web und db sind dabei mehr als nur Beschriftungen. Im normalen Compose-Netzwerk können sich die Container über diese Service-Namen gegenseitig erreichen.
image: welches Container-Image?
Mit image wird festgelegt, aus welchem Image der Container erstellt wird:
image: nginx:alpine
Der Teil hinter dem Doppelpunkt ist der Tag. Bei produktiven Anwendungen lohnt es sich oft, eine bewusst getestete Haupt- oder Patchversion zu verwenden, statt grundsätzlich überall latest einzusetzen.
container_name ist meistens nicht nötig
Früher habe ich fast jedem Dienst einen festen container_name gegeben. Das ist für normale Compose-Projekte gar nicht erforderlich.
Compose erzeugt selbst nachvollziehbare Namen aus Projekt und Service, beispielsweise meinprojekt-web-1. Für die Kommunikation zwischen Diensten verwendet man ohnehin besser den Service-Namen wie db.
ports: Host-Port und Container-Port
ports:
- "127.0.0.1:8080:80"
Die Schreibweise bedeutet hier:
127.0.0.1: nur auf dieser Host-Adresse lauschen8080: Port auf dem Docker-Host80: Port innerhalb des Containers
Gerade bei Reverse-Proxys nutze ich gern 127.0.0.1:8080:80. Dann ist der Dienst nicht direkt aus dem Internet erreichbar und Nginx kann lokal darauf zugreifen.
Dass das in der Praxis nützlich ist, siehst du zum Beispiel in meiner aktuellen Anleitung ONLYOFFICE Docs mit Docker Compose und Nextcloud einrichten.
volumes: Daten dauerhaft speichern
Container sind austauschbar. Wird ein Container neu erstellt, sollten wichtige Daten deshalb nicht ausschließlich in seinem beschreibbaren Container-Dateisystem liegen.
Ein Bind Mount sieht beispielsweise so aus:
volumes:
- ./db:/var/lib/mysql
Links steht der Pfad auf dem Host, rechts der Pfad im Container.
Alternativ gibt es benannte Docker-Volumes:
services:
db:
image: mariadb:11
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
Wichtig: Ein normaler Container-Neustart löscht Daten nicht plötzlich. Problematisch wird es vor allem, wenn ein Container entfernt und neu erzeugt wird und die wichtigen Daten vorher nicht persistent ausgelagert wurden.
environment: Einstellungen an den Container übergeben
environment:
MARIADB_DATABASE: wordpress
MARIADB_USER: wordpress
Environment-Variablen führen keine Kommandos aus. Sie übergeben Werte an das Programm im Container. Welche Variablen verfügbar sind, hängt vom jeweiligen Image ab.
Passwörter würde ich möglichst nicht direkt in eine öffentlich herumliegende Compose-Datei schreiben. Compose kann Werte beispielsweise aus einer .env-Datei übernehmen:
environment:
MARIADB_PASSWORD: "${DB_PASSWORD}"
networks: oft reicht das Standardnetzwerk
Eine der angenehmsten Compose-Funktionen passiert automatisch: Compose erstellt standardmäßig ein eigenes Bridge-Netzwerk für das Projekt. Alle Dienste darin können sich über ihren Service-Namen finden.
Ein Webcontainer kann seine Datenbank also beispielsweise über db:3306 ansprechen, wenn der Datenbank-Service db heißt. Feste Container-IP-Adressen sind dafür normalerweise nicht nötig.
Eigene Netzwerke werden erst interessant, wenn bestimmte Dienste bewusst voneinander getrennt oder mit einem externen Reverse-Proxy-Netz verbunden werden sollen.
links braucht man normalerweise nicht mehr
In meiner alten Anleitung gab es noch einen großen Abschnitt zu links. Für normale Compose-Projekte ist das heute nicht nötig. Dienste im selben Compose-Netzwerk erreichen sich bereits über ihre Service-Namen.
depends_on: Startreihenfolge, aber nicht automatisch Bereitschaft
services:
web:
image: meine-app
depends_on:
- db
db:
image: mariadb:11
Damit kennt Compose die Abhängigkeit zwischen web und db. Ein häufiger Denkfehler: Nur weil der Datenbankcontainer gestartet wurde, heißt das noch nicht automatisch, dass MariaDB bereits Verbindungen annimmt.
Wenn die tatsächliche Bereitschaft wichtig ist, kann man mit einem healthcheck und einer entsprechenden depends_on-Bedingung arbeiten.
restart: Was passiert nach einem Serverneustart?
restart: unless-stopped
unless-stopped ist für viele meiner dauerhaft laufenden Dienste eine praktische Wahl: Docker startet den Container wieder, solange ich ihn nicht bewusst gestoppt habe.
build: ein eigenes Dockerfile verwenden
services:
app:
build: ./app
Damit baut Compose das Image aus dem Dockerfile im Verzeichnis ./app. Das ist sinnvoll, wenn ein Standardimage angepasst oder eine eigene Anwendung containerisiert werden soll.
Die wichtigsten Compose-Befehle
# Starten oder Änderungen übernehmen
docker compose up -d
# Status anzeigen
docker compose ps
# Logs ansehen
docker compose logs -f --tail 100
# Images aktualisieren
docker compose pull
# Container stoppen und entfernen
docker compose down
# Konfiguration prüfen und aufgelöst anzeigen
docker compose config
Vorsicht mit docker compose down -v: Das zusätzliche -v entfernt auch die zum Projekt gehörenden benannten Volumes. Bei einer Datenbank kann das genau das sein, was man nicht wollte.
Ein praktisches Beispiel
Wer nach der Theorie direkt etwas ausprobieren möchte, kann sich meine aktuelle Pi-hole-v6-Anleitung mit Docker Compose ansehen. Dort sieht man Environment-Variablen, Ports und Volumes in einem echten kleinen Projekt.
Für die Container-Grundlagen habe ich außerdem Docker-Container für Einsteiger: starten, verwalten, Logs prüfen und stoppen aktualisiert.
Die vollständige aktuelle Referenz findest du in der Docker Compose Specification.
Gedanken dazu 0 Kommentare
Noch ist es ruhig hier. Dein Gedanke könnte der erste sein.
Was denkst du dazu?
Dein Kommentar erscheint nach der Freigabe.