YAML: Die Auszeichnungssprache in der Docker-Compose-Welt
docker-compose.yml — Referenz und Konzepte
Dieser Artikel erklärt den Aufbau und die Direktiven einer docker-compose.yml
auf Deutsch. Er richtet sich an alle die Docker Compose produktiv einsetzen und eine
kompakte Nachschlageseite in ihrer eigenen Sprache brauchen.
Die offizielle Referenz ist ausschließlich auf Englisch verfügbar: docs.docker.com/compose/compose-file
YAML — die Auszeichnungssprache
YAML steht für „YAML Ain't Markup Language" — ein rekursives Akronym, typisch für die Open-Source-Welt. Es ist eine Sprache zur Darstellung strukturierter Daten, die für Menschen lesbar sein soll.
YAML und JSON — Verwandtschaft, kein Dialekt
JSON und YAML sind verwandt, aber nicht dasselbe. Der häufige Vergleich „YAML ist ein JSON-Dialekt" ist ungenau:
- Jedes gültige JSON ist auch gültiges YAML — YAML ist eine Obermenge von JSON.
- Umgekehrt gilt das nicht: YAML kann Dinge ausdrücken die JSON nicht kann
(Kommentare, mehrzeilige Strings, Anker und Referenzen).
- Die Syntax ist völlig unterschiedlich: JSON verwendet geschweifte Klammern
und Anführungszeichen, YAML verwendet Einrückung und Doppelpunkte.
Dasselbe Datenkonstrukt in beiden Sprachen:
<syntaxhighlight lang="json"> {
"services": {
"nextcloud": {
"image": "nextcloud:latest",
"restart": "unless-stopped"
}
}
} </syntaxhighlight>
<syntaxhighlight lang="yaml"> services:
nextcloud: image: nextcloud:latest restart: unless-stopped
</syntaxhighlight>
YAML ist lesbarer — auf Kosten einer strengen Einrückungsregel: Einrückung mit Leerzeichen, niemals mit Tabs. Ein falsch gesetzter Tab ist der häufigste Fehler in compose-Dateien.
Kommentare
YAML unterstützt Kommentare mit # — JSON nicht.
Das macht YAML deutlich besser geeignet für Konfigurationsdateien die Menschen
pflegen:
<syntaxhighlight lang="yaml">
- Dieser Container darf keinen direkten Außenzugriff haben
ports: [] # keine ports → kein direkter Zugriff von außen </syntaxhighlight>
Datentypen
YAML erkennt Datentypen automatisch:
| Wert | Typ | Anmerkung |
|---|---|---|
true / false |
Boolean | Ohne Anführungszeichen |
42 |
Integer | |
3.14 |
Float | |
hallo |
String | Ohne Anführungszeichen wenn eindeutig |
"true" |
String | Mit Anführungszeichen erzwungen |
null |
Null |
Stolperfalle: restart: always ist ein String. restart: true
wäre ein Boolean — und ungültig. Im Zweifel Anführungszeichen setzen.
Grundaufbau einer docker-compose.yml
Eine docker-compose.yml besteht aus maximal vier Abschnitten
auf oberster Ebene:
<syntaxhighlight lang="yaml"> services: # Pflicht — definiert die Container
...
networks: # Optional — definiert Netzwerke
...
volumes: # Optional — definiert persistente Datenspeicher
...
configs: # Optional — definiert Konfigurationsdateien
...
</syntaxhighlight>
Der services-Abschnitt ist der einzig verpflichtende.
Alles andere ist optional und wird nur angegeben wenn benötigt.
Einrückungsregeln
Die Hierarchie wird ausschließlich durch Einrückung ausgedrückt. Zwei Leerzeichen pro Ebene sind Konvention:
<syntaxhighlight lang="yaml"> services: # Ebene 1
nextcloud: # Ebene 2 — Name des Dienstes
image: ... # Ebene 3 — Direktive des Dienstes
networks: # Ebene 3 — Direktive (Liste folgt)
- tuxi_net # Ebene 4 — Listenelement
</syntaxhighlight>
Direktiven im services-Abschnitt
image
Das Docker-Image das für den Container verwendet wird.
<syntaxhighlight lang="yaml"> services:
nextcloud: image: lscr.io/linuxserver/nextcloud:latest
</syntaxhighlight>
Format: registry/image:tag. Ohne Registry wird Docker Hub angenommen.
Ohne Tag wird latest angenommen — aber explizit angeben ist besser
für Reproduzierbarkeit.
container_name
Fester Name des Containers. Ohne diese Direktive generiert Docker Compose einen Namen aus Projektname + Servicename + Nummer.
<syntaxhighlight lang="yaml"> services:
nextcloud: container_name: nextcloud
</syntaxhighlight>
Wichtig: der Container-Name ist gleichzeitig der Docker-interne DNS-Name mit dem andere Container diesen Dienst ansprechen können.
restart
Neustart-Verhalten des Containers.
<syntaxhighlight lang="yaml"> restart: unless-stopped </syntaxhighlight>
| Wert | Verhalten |
|---|---|
no |
Nie neu starten (Standard) |
always |
Immer neu starten, auch nach manuellem Stopp |
unless-stopped |
Neu starten außer wenn manuell gestoppt — empfohlen für Produktionsdienste |
on-failure |
Nur neu starten wenn der Container mit Fehlercode endet |
image vs. build
Statt ein fertiges Image zu verwenden kann Docker Compose auch selbst eines bauen:
<syntaxhighlight lang="yaml"> services:
mein-dienst:
build:
context: ./mein-verzeichnis # Pfad zum Dockerfile
dockerfile: Dockerfile.prod # optional: anderer Dateiname
</syntaxhighlight>
ports
Portweiterleitung zwischen Host und Container: HOST:CONTAINER.
<syntaxhighlight lang="yaml"> services:
caddy:
ports:
- "80:80"
- "443:443"
- "443:443/udp" # für HTTP/3
</syntaxhighlight>
Wichtig: Wer ports weglässt, ist nur im Docker-Netzwerk erreichbar —
nicht von außen. Das ist für interne Dienste wie Datenbanken oder Collabora gewünscht.
volumes
Einbinden von Verzeichnissen oder benannten Volumes in den Container.
<syntaxhighlight lang="yaml"> services:
nextcloud:
volumes:
- ./nextcloud/app:/config # Bind Mount: Host-Pfad → Container-Pfad
- /data/nextcloud-data:/data # absoluter Host-Pfad
- caddy_data:/data # benanntes Volume (unten definiert)
volumes:
caddy_data: # benanntes Volume — Docker verwaltet den Speicherort
</syntaxhighlight>
| Typ | Syntax | Wann verwenden |
|---|---|---|
| Bind Mount | ./host/pfad:/container/pfad |
Wenn du direkten Zugriff auf die Dateien brauchst |
| Benanntes Volume | volume_name:/container/pfad |
Für Datenbankdaten, Caches — Docker verwaltet den Speicherort |
| tmpfs | Siehe tmpfs-Direktive |
Flüchtige Daten die nur im RAM leben sollen |
environment
Umgebungsvariablen die in den Container übergeben werden.
<syntaxhighlight lang="yaml"> services:
nextcloud:
environment:
- PUID=1000
- PGID=1001
- TZ=Europe/Berlin
- MYSQL_PASSWORD=${MYSQL_PASSWORD} # Wert aus .env-Datei
</syntaxhighlight>
Alternativ als Map-Schreibweise:
<syntaxhighlight lang="yaml">
environment:
PUID: "1000"
TZ: Europe/Berlin
</syntaxhighlight>
env_file
Umgebungsvariablen aus einer externen Datei laden statt sie in der compose-Datei zu hartcodieren — sinnvoll für Passwörter und Secrets:
<syntaxhighlight lang="yaml"> services:
nc-db:
env_file:
- .env
</syntaxhighlight>
Die .env-Datei enthält dann:
<syntaxhighlight lang="bash"> MYSQL_ROOT_PASSWORD=sehrgeheim MYSQL_DATABASE=nextcloud MYSQL_USER=ncuser MYSQL_PASSWORD=auchhgeheim </syntaxhighlight>
Die .env-Datei gehört in .gitignore — niemals in ein
öffentliches Repository einchecken.
networks
Netzwerkzugehörigkeit des Containers.
<syntaxhighlight lang="yaml"> services:
nextcloud:
networks:
tuxi_network:
ipv4_address: 172.18.0.20 # feste IP (optional)
collabora: networks: [tuxi_network] # Kurzschreibweise ohne feste IP
networks:
tuxi_network: external: true # Netzwerk existiert bereits, nicht neu anlegen
</syntaxhighlight>
Ohne networks-Angabe landet der Container im automatisch erstellten
Default-Netzwerk des Projekts — Container in verschiedenen Projekten können sich
dann nicht erreichen.
extra_hosts
Einträge die direkt in die /etc/hosts des Containers geschrieben werden.
Überschreibt DNS — wird vor jeder DNS-Anfrage ausgewertet.
<syntaxhighlight lang="yaml"> services:
nextcloud:
extra_hosts:
- "collabora.beispiel.de:172.18.0.6"
</syntaxhighlight>
Typischer Anwendungsfall: Hairpinning vermeiden — ein Container soll eine Domain intern auflösen statt den Umweg über die externe IP zu nehmen. Siehe Hairpin-NAT – Collabora und Nextcloud hinter Caddy.
depends_on
Startreihenfolge und Abhängigkeiten zwischen Diensten.
<syntaxhighlight lang="yaml"> services:
nextcloud:
depends_on:
db:
condition: service_healthy # wartet bis Healthcheck grün ist
redis:
condition: service_healthy
</syntaxhighlight>
| condition | Bedeutung |
|---|---|
service_started |
Container wurde gestartet (Standard — prüft nicht ob er wirklich läuft) |
service_healthy |
Healthcheck des abhängigen Dienstes ist grün |
service_completed_successfully |
Abhängiger Dienst hat sich mit Exit-Code 0 beendet |
healthcheck
Prüft ob der Container wirklich funktioniert — nicht nur ob er läuft.
<syntaxhighlight lang="yaml"> services:
nc-db:
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD || exit 1"]
interval: 10s # wie oft prüfen
timeout: 5s # wie lange auf Antwort warten
retries: 30 # wie oft wiederholen bevor unhealthy
start_period: 30s # Anlaufzeit bevor Healthcheck zählt
</syntaxhighlight>
Das $$ ist kein Tippfehler — doppeltes Dollar-Zeichen verhindert
dass Compose die Variable selbst auflöst; sie wird stattdessen an die Shell
im Container übergeben.
tmpfs
Flüchtige Dateisysteme die nur im RAM leben und beim Container-Stopp verschwinden. Für temporäre Daten, Caches und Security-Härtung:
<syntaxhighlight lang="yaml"> services:
nc-redis:
tmpfs:
- /data
- /tmp
- /run
collabora:
tmpfs:
- /tmp
- /var/tmp
</syntaxhighlight>
Redis im RAM-only-Modus: schneller, kein Disk-I/O, Daten gehen beim Neustart verloren — für Session-Caches gewünscht.
dns
DNS-Server die der Container verwenden soll statt des Docker-Defaults.
<syntaxhighlight lang="yaml"> services:
nextcloud:
dns:
- 1.1.1.1 # Cloudflare
- 9.9.9.9 # Quad9
- 8.8.8.8 # Google
</syntaxhighlight>
command
Überschreibt den Standard-Startbefehl des Images.
<syntaxhighlight lang="yaml"> services:
nc-db: command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
</syntaxhighlight>
security_opt und cap_drop / cap_add
Container-Härtung: Linux-Capabilities entziehen oder hinzufügen.
<syntaxhighlight lang="yaml"> services:
nc-redis:
security_opt:
- no-new-privileges:true # verhindert Privilege Escalation
cap_drop:
- ALL # alle Capabilities entziehen
cap_add:
- NET_BIND_SERVICE # nur diese eine wieder erlauben (z.B. für Ports <1024)
</syntaxhighlight>
Faustregel aus dem Kommentarteil deiner compose-Datei:
no-new-privileges: true— immer möglich, immer gutcap_drop: ALL— wo immer es gehtread_only: true— nur bei schlanken Services ohne viele Schreibpfade
read_only
Container-Dateisystem schreibgeschützt — kombiniert mit tmpfs für
Verzeichnisse die Schreibzugriff benötigen:
<syntaxhighlight lang="yaml"> services:
nc-redis:
read_only: true
tmpfs:
- /data
- /tmp
- /run
</syntaxhighlight>
Der networks-Abschnitt
Externes Netzwerk verwenden
<syntaxhighlight lang="yaml"> networks:
tuxi_network: external: true # Netzwerk wurde bereits mit docker network create angelegt
</syntaxhighlight>
Neues Netzwerk anlegen
<syntaxhighlight lang="yaml"> networks:
mein_netzwerk:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
</syntaxhighlight>
Der volumes-Abschnitt
Benanntes Volume
<syntaxhighlight lang="yaml"> volumes:
caddy_data: # Docker verwaltet den Speicherort unter /var/lib/docker/volumes/ caddy_config:
</syntaxhighlight>
Volume mit tmpfs-Treiber (RAM-Disk)
<syntaxhighlight lang="yaml"> volumes:
cool-child-roots:
driver_opts:
type: tmpfs
device: tmpfs
o: "size=4g,mode=1777"
</syntaxhighlight>
Nützliche CLI-Befehle
| Befehl | Was er tut |
|---|---|
docker compose up -d |
Alle Dienste im Hintergrund starten |
docker compose down |
Alle Dienste stoppen und Container entfernen |
docker compose restart |
Alle Dienste neu starten |
docker compose restart caddy |
Nur einen Dienst neu starten |
docker compose up -d --force-recreate nextcloud |
Einen Container neu erstellen (bei Konfigurationsänderungen) |
docker compose logs -f nextcloud |
Log eines Dienstes live verfolgen |
docker compose ps |
Status aller Dienste anzeigen |
docker compose exec nextcloud bash |
Shell in laufendem Container öffnen |
docker compose pull |
Alle Images aktualisieren |
Häufige Fehler
| Fehler | Ursache | Fix |
|---|---|---|
yaml: line X: found character that cannot start any token |
Tab statt Leerzeichen zur Einrückung | Alle Tabs durch Leerzeichen ersetzen |
Address already in use |
Feste IP bereits von anderem Container belegt | docker network inspect → Schuldigen finden
|
| Container startet, aber Dienst funktioniert nicht | depends_on ohne condition: service_healthy |
Healthcheck ergänzen und service_healthy verwenden
|
Umgebungsvariable leer obwohl in .env gesetzt |
.env liegt nicht im selben Verzeichnis wie die compose-Datei |
Pfad prüfen oder env_file explizit angeben
|
$$VARIABLE wird nicht aufgelöst |
Gewolltes Verhalten — $$ wird zu $ und an die Shell übergeben |
Für Shell-Variablen im Container: $$ verwenden. Für Compose-Variablen: $
|
