YAML: Die Auszeichnungssprache in der Docker-Compose-Welt: Unterschied zwischen den Versionen

Aus Tuxipedia
Die Seite wurde neu angelegt: „== docker-compose.yml — Referenz und Konzepte == Dieser Artikel erklärt den Aufbau und die Direktiven einer <code>docker-compose.yml</code> 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: [https://docs.docker.com/compose/compose-file/ docs.docker.com/compose/compose-file] ---- == YAML…“
 
 
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt)
Zeile 1: Zeile 1:
== docker-compose.yml — Referenz und Konzepte ==
== YAML — Die Auszeichnungssprache in der Docker-Compose-Welt ==


Dieser Artikel erklärt den Aufbau und die Direktiven einer <code>docker-compose.yml</code>
YAML steht für „YAML Ain't Markup Language" — ein rekursives Akronym, typisch für
auf Deutsch. Er richtet sich an alle die Docker Compose produktiv einsetzen und eine
die Open-Source-Welt. Es ist eine Sprache zur Darstellung strukturierter Daten,
kompakte Nachschlageseite in ihrer eigenen Sprache brauchen.
die für Menschen lesbar sein soll.
 
----
 
== Geschichte ==
 
YAML wurde 2001 von drei Entwicklern entworfen: Clark Evans, Ingy döt Net
und Oren Ben-Kiki. Das ursprüngliche Akronym lautete noch „Yet Another Markup
Language" — in Anlehnung an die vielen XML-Varianten der Zeit. Es wurde bald
zu „YAML Ain't Markup Language" umgedeutet, um klarzustellen: das hier ist
keine Dokumentensprache wie HTML oder XML, sondern eine reine Datensprache.
 
Das Grundproblem das YAML lösen wollte war real: XML war für Konfigurationsdateien
viel zu geschwätzig — jeder Wert brauchte ein öffnendes und schließendes Tag.
JSON existierte noch gar nicht (das kam erst 2002). Man wollte etwas das Menschen
wirklich lesen und schreiben können, ohne ständig spitze Klammern tippen zu müssen.
 
=== Verbreitung vor Docker ===
 
Docker hat YAML nicht erfunden — es hat YAML einer neuen Generation bekannt gemacht.
Vorher war YAML in anderen Welten zuhause:
 
* '''Ruby on Rails''' (2004) — der erste große Durchbruch. Rails verwendete YAML
  für Datenbankverbindungen, Übersetzungen und Konfiguration. Eine ganze Generation
  von Webentwicklern lernte YAML über Rails, lange vor Docker.
 
* '''Ansible''' (2012) — das Konfigurationsmanagement-Tool wählte YAML als zentrale
  Sprache für Playbooks. Für Systemadministratoren war Ansible der eigentliche
  YAML-Einstieg.
 
* '''Kubernetes''' (2014) — parallel zu Docker entstanden, ebenfalls YAML für alles.
  Kubernetes hat YAML in der Infrastrukturwelt zementiert.
 
* '''CI/CD-Systeme''' — GitHub Actions, GitLab CI, Travis CI, CircleCI — alle
  verwenden YAML für Pipeline-Definitionen.
 
* '''OpenAPI/Swagger''' — API-Dokumentation wird seit Jahren in YAML geschrieben.
 
Docker Compose hat YAML dann für Heimserver-Betreiber und Administratoren ohne
Rails- oder Ansible-Hintergrund zum Standard gemacht.
 
=== Kritik in der Fachwelt ===
 
YAML hat einen schlechten Ruf in Teilen der Entwicklergemeinde — nicht wegen
der Grundidee, sondern wegen der Implementierung.
 
Die Spezifikation ist notorisch komplex: YAML 1.2 umfasst über 80 Seiten.
Verschiedene Parser verhalten sich bei Grenzfällen unterschiedlich. Und es gibt
berühmte Stolperfallen:
 
<pre>
# Diese Werte sind KEIN String — YAML interpretiert sie als Boolean:
land: no        # → false
antwort: yes    # → true
aktiv: on      # → true
 
# Das hier ist kein String — es wird zur Zahl:
version: 1.0    # → Float 1.0, nicht String "1.0"
port: 8080      # → Integer
</pre>


Die offizielle Referenz ist ausschließlich auf Englisch verfügbar:
Das bekannteste Beispiel ist das sogenannte '''Norway Problem''': das Länderkürzel
[https://docs.docker.com/compose/compose-file/ docs.docker.com/compose/compose-file]
<code>NO</code> wird in älteren YAML-Parsern automatisch zu <code>false</code>
— was dazu geführt hat dass norwegische Entwickler in Konfigurationsdateien
ihren eigenen Ländercode nicht als String verwenden konnten ohne Anführungszeichen.🤣


----
YAML 1.2 (2009) hat viele dieser Probleme behoben, aber ältere Parser sind
noch weit verbreitet.


== YAML — die Auszeichnungssprache ==
Das Fazit vieler erfahrener Entwickler: YAML ist für Menschen gut lesbar,
aber für Programme schwer korrekt zu implementieren. Für Konfigurationsdateien
die selten geändert werden ist es hervorragend für Programme die YAML
generieren oder parsen müssen, ist es eine Quelle von Überraschungen.


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 ===
== YAML und JSON — Verwandtschaft, kein Dialekt ==


JSON und YAML sind '''verwandt, aber nicht dasselbe'''. Der häufige Vergleich
Der häufige Vergleich „YAML ist ein JSON-Dialekt" ist ungenau.
„YAML ist ein JSON-Dialekt" ist ungenau:
Die präzise Formulierung: '''YAML ist eine Obermenge von JSON.'''


* Jedes gültige JSON ist auch gültiges YAML — YAML ist eine Obermenge von JSON.
* Jedes gültige JSON ist auch gültiges YAML.
* Umgekehrt gilt das nicht: YAML kann Dinge ausdrücken die JSON nicht kann
* Umgekehrt gilt das nicht: YAML kann Dinge ausdrücken die JSON nicht kann
   (Kommentare, mehrzeilige Strings, Anker und Referenzen).
   (Kommentare, mehrzeilige Strings, Anker und Referenzen).
Zeile 29: Zeile 92:
Dasselbe Datenkonstrukt in beiden Sprachen:
Dasselbe Datenkonstrukt in beiden Sprachen:


<syntaxhighlight lang="json">
JSON:
<pre>
{
{
   "services": {
   "services": {
Zeile 38: Zeile 102:
   }
   }
}
}
</syntaxhighlight>
</pre>


<syntaxhighlight lang="yaml">
YAML:
<pre>
services:
services:
   nextcloud:
   nextcloud:
     image: nextcloud:latest
     image: nextcloud:latest
     restart: unless-stopped
     restart: unless-stopped
</syntaxhighlight>
</pre>


YAML ist lesbarer — auf Kosten einer strengen Einrückungsregel: '''Einrückung
YAML ist lesbarer — auf Kosten einer strengen Regel:
mit Leerzeichen, niemals mit Tabs.''' Ein falsch gesetzter Tab ist der häufigste
'''Einrückung mit Leerzeichen, niemals mit Tabs.'''
Fehler in compose-Dateien.
Ein falsch gesetzter Tab ist der häufigste Fehler in compose-Dateien.


=== Kommentare ===
=== Kommentare ===
Zeile 57: Zeile 122:
pflegen:
pflegen:


<syntaxhighlight lang="yaml">
<pre>
# Dieser Container darf keinen direkten Außenzugriff haben
# Dieser Container darf keinen direkten Außenzugriff haben
ports: []  # keine ports → kein direkter Zugriff von außen
ports: []  # keine ports → kein direkter Zugriff von außen
</syntaxhighlight>
</pre>


=== Datentypen ===
=== Datentypen ===
Zeile 82: Zeile 147:
|}
|}


Stolperfalle: <code>restart: always</code> ist ein String. <code>restart: true</code>
Stolperfalle: <code>restart: always</code> ist ein String.
wäre ein Boolean — und ungültig. Im Zweifel Anführungszeichen setzen.
<code>restart: true</code> wäre ein Boolean — und ungültig.
Im Zweifel Anführungszeichen setzen.


----
----
Zeile 92: Zeile 158:
auf oberster Ebene:
auf oberster Ebene:


<syntaxhighlight lang="yaml">
<pre>
services:      # Pflicht — definiert die Container
services:      # Pflicht — definiert die Container
   ...
   ...
Zeile 104: Zeile 170:
configs:      # Optional — definiert Konfigurationsdateien
configs:      # Optional — definiert Konfigurationsdateien
   ...
   ...
</syntaxhighlight>
</pre>


Der <code>services</code>-Abschnitt ist der einzig verpflichtende.
Der <code>services</code>-Abschnitt ist der einzig verpflichtende.
Zeile 114: Zeile 180:
Zwei Leerzeichen pro Ebene sind Konvention:
Zwei Leerzeichen pro Ebene sind Konvention:


<syntaxhighlight lang="yaml">
<pre>
services:          # Ebene 1
services:          # Ebene 1
   nextcloud:        # Ebene 2 — Name des Dienstes
   nextcloud:        # Ebene 2 — Name des Dienstes
Zeile 120: Zeile 186:
     networks:      # Ebene 3 — Direktive (Liste folgt)
     networks:      # Ebene 3 — Direktive (Liste folgt)
       - tuxi_net    # Ebene 4 — Listenelement
       - tuxi_net    # Ebene 4 — Listenelement
</syntaxhighlight>
</pre>


----
----
Zeile 130: Zeile 196:
Das Docker-Image das für den Container verwendet wird.
Das Docker-Image das für den Container verwendet wird.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nextcloud:
   nextcloud:
     image: lscr.io/linuxserver/nextcloud:latest
     image: lscr.io/linuxserver/nextcloud:latest
</syntaxhighlight>
</pre>


Format: <code>registry/image:tag</code>. Ohne Registry wird Docker Hub angenommen.
Format: <code>registry/image:tag</code>. Ohne Registry wird Docker Hub angenommen.
Zeile 145: Zeile 211:
einen Namen aus Projektname + Servicename + Nummer.
einen Namen aus Projektname + Servicename + Nummer.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nextcloud:
   nextcloud:
     container_name: nextcloud
     container_name: nextcloud
</syntaxhighlight>
</pre>


Wichtig: der Container-Name ist gleichzeitig der Docker-interne DNS-Name
Wichtig: der Container-Name ist gleichzeitig der Docker-interne DNS-Name
Zeile 158: Zeile 224:
Neustart-Verhalten des Containers.
Neustart-Verhalten des Containers.


<syntaxhighlight lang="yaml">
<pre>
restart: unless-stopped
restart: unless-stopped
</syntaxhighlight>
</pre>


{| class="wikitable"
{| class="wikitable"
Zeile 178: Zeile 244:
Statt ein fertiges Image zu verwenden kann Docker Compose auch selbst eines bauen:
Statt ein fertiges Image zu verwenden kann Docker Compose auch selbst eines bauen:


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   mein-dienst:
   mein-dienst:
Zeile 184: Zeile 250:
       context: ./mein-verzeichnis  # Pfad zum Dockerfile
       context: ./mein-verzeichnis  # Pfad zum Dockerfile
       dockerfile: Dockerfile.prod  # optional: anderer Dateiname
       dockerfile: Dockerfile.prod  # optional: anderer Dateiname
</syntaxhighlight>
</pre>


=== ports ===
=== ports ===
Zeile 190: Zeile 256:
Portweiterleitung zwischen Host und Container: <code>HOST:CONTAINER</code>.
Portweiterleitung zwischen Host und Container: <code>HOST:CONTAINER</code>.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   caddy:
   caddy:
Zeile 197: Zeile 263:
       - "443:443"
       - "443:443"
       - "443:443/udp"  # für HTTP/3
       - "443:443/udp"  # für HTTP/3
</syntaxhighlight>
</pre>


'''Wichtig:''' Wer <code>ports</code> weglässt, ist nur im Docker-Netzwerk erreichbar —
'''Wichtig:''' Wer <code>ports</code> weglässt, ist nur im Docker-Netzwerk erreichbar —
Zeile 206: Zeile 272:
Einbinden von Verzeichnissen oder benannten Volumes in den Container.
Einbinden von Verzeichnissen oder benannten Volumes in den Container.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nextcloud:
   nextcloud:
     volumes:
     volumes:
       - ./nextcloud/app:/config         # Bind Mount: Host-Pfad → Container-Pfad
       - ./nextcloud/app:/config       # Bind Mount: Host-Pfad → Container-Pfad
       - /data/nextcloud-data:/data       # absoluter Host-Pfad
       - /data/nextcloud-data:/data   # absoluter Host-Pfad
       - caddy_data:/data                 # benanntes Volume (unten definiert)
       - caddy_data:/data             # benanntes Volume (unten definiert)


volumes:
volumes:
   caddy_data:                           # benanntes Volume — Docker verwaltet den Speicherort
   caddy_data:                         # Docker verwaltet den Speicherort
</syntaxhighlight>
</pre>


{| class="wikitable"
{| class="wikitable"
Zeile 232: Zeile 298:
Umgebungsvariablen die in den Container übergeben werden.
Umgebungsvariablen die in den Container übergeben werden.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nextcloud:
   nextcloud:
Zeile 240: Zeile 306:
       - TZ=Europe/Berlin
       - TZ=Europe/Berlin
       - MYSQL_PASSWORD=${MYSQL_PASSWORD}  # Wert aus .env-Datei
       - MYSQL_PASSWORD=${MYSQL_PASSWORD}  # Wert aus .env-Datei
</syntaxhighlight>
</pre>


Alternativ als Map-Schreibweise:
Alternativ als Map-Schreibweise:


<syntaxhighlight lang="yaml">
<pre>
     environment:
     environment:
       PUID: "1000"
       PUID: "1000"
       TZ: Europe/Berlin
       TZ: Europe/Berlin
</syntaxhighlight>
</pre>


=== env_file ===
=== env_file ===


Umgebungsvariablen aus einer externen Datei laden statt sie in der compose-Datei
Umgebungsvariablen aus einer externen Datei laden — sinnvoll für Passwörter:
zu hartcodieren — sinnvoll für Passwörter und Secrets:


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nc-db:
   nc-db:
     env_file:
     env_file:
       - .env
       - .env
</syntaxhighlight>
</pre>


Die <code>.env</code>-Datei enthält dann:
Die <code>.env</code>-Datei enthält dann:


<syntaxhighlight lang="bash">
<pre>
MYSQL_ROOT_PASSWORD=sehrgeheim
MYSQL_ROOT_PASSWORD=sehrgeheim
MYSQL_DATABASE=nextcloud
MYSQL_DATABASE=nextcloud
MYSQL_USER=ncuser
MYSQL_USER=ncuser
MYSQL_PASSWORD=auchhgeheim
MYSQL_PASSWORD=auchgeheim
</syntaxhighlight>
</pre>


Die <code>.env</code>-Datei gehört in <code>.gitignore</code> — niemals in ein
Die <code>.env</code>-Datei gehört in <code>.gitignore</code> — niemals in ein
Zeile 278: Zeile 343:
Netzwerkzugehörigkeit des Containers.
Netzwerkzugehörigkeit des Containers.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nextcloud:
   nextcloud:
     networks:
     networks:
       tuxi_network:
       mein_netzwerk:
         ipv4_address: 172.18.0.20  # feste IP (optional)
         ipv4_address: 172.18.0.20  # feste IP (optional)


   collabora:
   collabora:
     networks: [tuxi_network]       # Kurzschreibweise ohne feste IP
     networks: [mein_netzwerk]       # Kurzschreibweise ohne feste IP


networks:
networks:
   tuxi_network:
   mein_netzwerk:
     external: true                  # Netzwerk existiert bereits, nicht neu anlegen
     external: true                  # Netzwerk existiert bereits
</syntaxhighlight>
</pre>


Ohne <code>networks</code>-Angabe landet der Container im automatisch erstellten
Ohne <code>networks</code>-Angabe landet der Container im automatisch erstellten
Zeile 302: Zeile 367:
Überschreibt DNS — wird vor jeder DNS-Anfrage ausgewertet.
Überschreibt DNS — wird vor jeder DNS-Anfrage ausgewertet.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nextcloud:
   nextcloud:
     extra_hosts:
     extra_hosts:
       - "collabora.beispiel.de:172.18.0.6"
       - "collabora.beispiel.de:172.18.0.6"
</syntaxhighlight>
</pre>


Typischer Anwendungsfall: Hairpinning vermeiden — ein Container soll eine Domain
Typischer Anwendungsfall: Hairpinning vermeiden — ein Container soll eine Domain
Zeile 317: Zeile 382:
Startreihenfolge und Abhängigkeiten zwischen Diensten.
Startreihenfolge und Abhängigkeiten zwischen Diensten.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nextcloud:
   nextcloud:
Zeile 325: Zeile 390:
       redis:
       redis:
         condition: service_healthy
         condition: service_healthy
</syntaxhighlight>
</pre>


{| class="wikitable"
{| class="wikitable"
Zeile 341: Zeile 406:
Prüft ob der Container wirklich funktioniert — nicht nur ob er läuft.
Prüft ob der Container wirklich funktioniert — nicht nur ob er läuft.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nc-db:
   nc-db:
Zeile 350: Zeile 415:
       retries: 30        # wie oft wiederholen bevor unhealthy
       retries: 30        # wie oft wiederholen bevor unhealthy
       start_period: 30s  # Anlaufzeit bevor Healthcheck zählt
       start_period: 30s  # Anlaufzeit bevor Healthcheck zählt
</syntaxhighlight>
</pre>


Das <code>$$</code> ist kein Tippfehler — doppeltes Dollar-Zeichen verhindert
Das <code>$$</code> ist kein Tippfehler — doppeltes Dollar-Zeichen verhindert
dass Compose die Variable selbst auflöst; sie wird stattdessen an die Shell
dass Compose die Variable selbst auflöst; sie wird an die Shell im Container
im Container übergeben.
übergeben.


=== tmpfs ===
=== tmpfs ===


Flüchtige Dateisysteme die nur im RAM leben und beim Container-Stopp verschwinden.
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">
<pre>
services:
services:
   nc-redis:
   nc-redis:
Zeile 373: Zeile 437:
       - /tmp
       - /tmp
       - /var/tmp
       - /var/tmp
</syntaxhighlight>
</pre>


Redis im RAM-only-Modus: schneller, kein Disk-I/O, Daten gehen beim Neustart verloren —
Redis im RAM-only-Modus: schneller, kein Disk-I/O, Daten gehen beim Neustart verloren —
Zeile 382: Zeile 446:
DNS-Server die der Container verwenden soll statt des Docker-Defaults.
DNS-Server die der Container verwenden soll statt des Docker-Defaults.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nextcloud:
   nextcloud:
Zeile 389: Zeile 453:
       - 9.9.9.9    # Quad9
       - 9.9.9.9    # Quad9
       - 8.8.8.8    # Google
       - 8.8.8.8    # Google
</syntaxhighlight>
</pre>


=== command ===
=== command ===
Zeile 395: Zeile 459:
Überschreibt den Standard-Startbefehl des Images.
Überschreibt den Standard-Startbefehl des Images.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nc-db:
   nc-db:
     command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
     command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
</syntaxhighlight>
</pre>


=== security_opt und cap_drop / cap_add ===
=== security_opt und cap_drop / cap_add ===
Zeile 405: Zeile 469:
Container-Härtung: Linux-Capabilities entziehen oder hinzufügen.
Container-Härtung: Linux-Capabilities entziehen oder hinzufügen.


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nc-redis:
   nc-redis:
Zeile 413: Zeile 477:
       - ALL                      # alle Capabilities entziehen
       - ALL                      # alle Capabilities entziehen
     cap_add:
     cap_add:
       - NET_BIND_SERVICE        # nur diese eine wieder erlauben (z.B. für Ports <1024)
       - NET_BIND_SERVICE        # nur diese eine wieder erlauben
</syntaxhighlight>
</pre>


Faustregel aus dem Kommentarteil deiner compose-Datei:
Faustregel:
* <code>no-new-privileges: true</code> — immer möglich, immer gut
* <code>no-new-privileges: true</code> — immer möglich, immer gut
* <code>cap_drop: ALL</code> — wo immer es geht
* <code>cap_drop: ALL</code> — wo immer es geht
Zeile 426: Zeile 490:
Verzeichnisse die Schreibzugriff benötigen:
Verzeichnisse die Schreibzugriff benötigen:


<syntaxhighlight lang="yaml">
<pre>
services:
services:
   nc-redis:
   nc-redis:
Zeile 434: Zeile 498:
       - /tmp
       - /tmp
       - /run
       - /run
</syntaxhighlight>
</pre>


----
----
Zeile 442: Zeile 506:
=== Externes Netzwerk verwenden ===
=== Externes Netzwerk verwenden ===


<syntaxhighlight lang="yaml">
<pre>
networks:
networks:
   tuxi_network:
   mein_netzwerk:
     external: true  # Netzwerk wurde bereits mit docker network create angelegt
     external: true  # Netzwerk wurde bereits mit docker network create angelegt
</syntaxhighlight>
</pre>


=== Neues Netzwerk anlegen ===
=== Neues Netzwerk anlegen ===


<syntaxhighlight lang="yaml">
<pre>
networks:
networks:
   mein_netzwerk:
   mein_netzwerk:
Zeile 458: Zeile 522:
         - subnet: 172.20.0.0/16
         - subnet: 172.20.0.0/16
           gateway: 172.20.0.1
           gateway: 172.20.0.1
</syntaxhighlight>
</pre>


----
----
Zeile 466: Zeile 530:
=== Benanntes Volume ===
=== Benanntes Volume ===


<syntaxhighlight lang="yaml">
<pre>
volumes:
volumes:
   caddy_data:      # Docker verwaltet den Speicherort unter /var/lib/docker/volumes/
   caddy_data:      # Docker verwaltet den Speicherort unter /var/lib/docker/volumes/
   caddy_config:
   caddy_config:
</syntaxhighlight>
</pre>


=== Volume mit tmpfs-Treiber (RAM-Disk) ===
=== Volume mit tmpfs-Treiber (RAM-Disk) ===


<syntaxhighlight lang="yaml">
<pre>
volumes:
volumes:
   cool-child-roots:
   cool-child-roots:
Zeile 481: Zeile 545:
       device: tmpfs
       device: tmpfs
       o: "size=4g,mode=1777"
       o: "size=4g,mode=1777"
</syntaxhighlight>
</pre>


----
----
Zeile 498: Zeile 562:
| <code>docker compose restart caddy</code> || Nur einen Dienst neu starten
| <code>docker compose restart caddy</code> || Nur einen Dienst neu starten
|-
|-
| <code>docker compose up -d --force-recreate nextcloud</code> || Einen Container neu erstellen (bei Konfigurationsänderungen)
| <code>docker compose up -d --force-recreate nextcloud</code> || Einen Container neu erstellen
|-
|-
| <code>docker compose logs -f nextcloud</code> || Log eines Dienstes live verfolgen
| <code>docker compose logs -f nextcloud</code> || Log eines Dienstes live verfolgen
Zeile 516: Zeile 580:
! Fehler !! Ursache !! Fix
! Fehler !! Ursache !! Fix
|-
|-
| <code>yaml: line X: found character that cannot start any token</code> || Tab statt Leerzeichen zur Einrückung || Alle Tabs durch Leerzeichen ersetzen
| <code>yaml: line X: found character that cannot start any token</code> || Tab statt Leerzeichen || Alle Tabs durch Leerzeichen ersetzen
|-
|-
| <code>Address already in use</code> || Feste IP bereits von anderem Container belegt || <code>docker network inspect</code> → Schuldigen finden
| <code>Address already in use</code> || Feste IP bereits von anderem Container belegt || <code>docker network inspect</code> → Schuldigen finden
|-
|-
| Container startet, aber Dienst funktioniert nicht || <code>depends_on</code> ohne <code>condition: service_healthy</code> || Healthcheck ergänzen und <code>service_healthy</code> verwenden
| Container startet, Dienst funktioniert nicht || <code>depends_on</code> ohne <code>condition: service_healthy</code> || Healthcheck ergänzen
|-
|-
| Umgebungsvariable leer obwohl in <code>.env</code> gesetzt || <code>.env</code> liegt nicht im selben Verzeichnis wie die compose-Datei || Pfad prüfen oder <code>env_file</code> explizit angeben
| Umgebungsvariable leer || <code>.env</code> liegt nicht im selben Verzeichnis || Pfad prüfen oder <code>env_file</code> explizit angeben
|-
|-
| <code>$$VARIABLE</code> wird nicht aufgelöst || Gewolltes Verhalten — <code>$$</code> wird zu <code>$</code> und an die Shell übergeben || Für Shell-Variablen im Container: <code>$$</code> verwenden. Für Compose-Variablen: <code>$</code>
| <code>$$VARIABLE</code> wird nicht aufgelöst || Gewolltes Verhalten || Für Shell-Variablen im Container: <code>$$</code>. Für Compose-Variablen: <code>$</code>
|}
|}


Zeile 532: Zeile 596:


* [[Webserver, Reverse Proxy und Container – wie die Schichten zusammenspielen]]
* [[Webserver, Reverse Proxy und Container – wie die Schichten zusammenspielen]]
* [[Docker-Netzwerke und Container-IPs — Konfiguration und Fallstricke]]
* [[Docker-Netzwerke und Container-IPs]]
* [[Hairpin-NAT – Collabora und Nextcloud hinter Caddy]]
* [[Hairpin-NAT – Collabora und Nextcloud hinter Caddy]]
* [https://docs.docker.com/compose/compose-file/ Offizielle Compose-Referenz (englisch)]
* [https://docs.docker.com/compose/compose-file/ Offizielle Compose-Referenz (englisch)]


[[Kategorie:Docker]]
[[Kategorie:Docker]]

Aktuelle Version vom 24. April 2026, 12:20 Uhr

YAML — Die Auszeichnungssprache in der Docker-Compose-Welt

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.


Geschichte

YAML wurde 2001 von drei Entwicklern entworfen: Clark Evans, Ingy döt Net und Oren Ben-Kiki. Das ursprüngliche Akronym lautete noch „Yet Another Markup Language" — in Anlehnung an die vielen XML-Varianten der Zeit. Es wurde bald zu „YAML Ain't Markup Language" umgedeutet, um klarzustellen: das hier ist keine Dokumentensprache wie HTML oder XML, sondern eine reine Datensprache.

Das Grundproblem das YAML lösen wollte war real: XML war für Konfigurationsdateien viel zu geschwätzig — jeder Wert brauchte ein öffnendes und schließendes Tag. JSON existierte noch gar nicht (das kam erst 2002). Man wollte etwas das Menschen wirklich lesen und schreiben können, ohne ständig spitze Klammern tippen zu müssen.

Verbreitung vor Docker

Docker hat YAML nicht erfunden — es hat YAML einer neuen Generation bekannt gemacht. Vorher war YAML in anderen Welten zuhause:

  • Ruby on Rails (2004) — der erste große Durchbruch. Rails verwendete YAML
 für Datenbankverbindungen, Übersetzungen und Konfiguration. Eine ganze Generation
 von Webentwicklern lernte YAML über Rails, lange vor Docker.
  • Ansible (2012) — das Konfigurationsmanagement-Tool wählte YAML als zentrale
 Sprache für Playbooks. Für Systemadministratoren war Ansible der eigentliche
 YAML-Einstieg.
  • Kubernetes (2014) — parallel zu Docker entstanden, ebenfalls YAML für alles.
 Kubernetes hat YAML in der Infrastrukturwelt zementiert.
  • CI/CD-Systeme — GitHub Actions, GitLab CI, Travis CI, CircleCI — alle
 verwenden YAML für Pipeline-Definitionen.
  • OpenAPI/Swagger — API-Dokumentation wird seit Jahren in YAML geschrieben.

Docker Compose hat YAML dann für Heimserver-Betreiber und Administratoren ohne Rails- oder Ansible-Hintergrund zum Standard gemacht.

Kritik in der Fachwelt

YAML hat einen schlechten Ruf in Teilen der Entwicklergemeinde — nicht wegen der Grundidee, sondern wegen der Implementierung.

Die Spezifikation ist notorisch komplex: YAML 1.2 umfasst über 80 Seiten. Verschiedene Parser verhalten sich bei Grenzfällen unterschiedlich. Und es gibt berühmte Stolperfallen:

# Diese Werte sind KEIN String — YAML interpretiert sie als Boolean:
land: no        # → false
antwort: yes    # → true
aktiv: on       # → true

# Das hier ist kein String — es wird zur Zahl:
version: 1.0    # → Float 1.0, nicht String "1.0"
port: 8080      # → Integer

Das bekannteste Beispiel ist das sogenannte Norway Problem: das Länderkürzel NO wird in älteren YAML-Parsern automatisch zu false — was dazu geführt hat dass norwegische Entwickler in Konfigurationsdateien ihren eigenen Ländercode nicht als String verwenden konnten ohne Anführungszeichen.🤣

YAML 1.2 (2009) hat viele dieser Probleme behoben, aber ältere Parser sind noch weit verbreitet.

Das Fazit vieler erfahrener Entwickler: YAML ist für Menschen gut lesbar, aber für Programme schwer korrekt zu implementieren. Für Konfigurationsdateien die selten geändert werden ist es hervorragend — für Programme die YAML generieren oder parsen müssen, ist es eine Quelle von Überraschungen.


YAML und JSON — Verwandtschaft, kein Dialekt

Der häufige Vergleich „YAML ist ein JSON-Dialekt" ist ungenau. Die präzise Formulierung: YAML ist eine Obermenge von JSON.

  • Jedes gültige JSON ist auch gültiges YAML.
  • 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:

JSON:

{
  "services": {
    "nextcloud": {
      "image": "nextcloud:latest",
      "restart": "unless-stopped"
    }
  }
}

YAML:

services:
  nextcloud:
    image: nextcloud:latest
    restart: unless-stopped

YAML ist lesbarer — auf Kosten einer strengen Regel: 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:

# Dieser Container darf keinen direkten Außenzugriff haben
ports: []  # keine ports → kein direkter Zugriff von außen

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:

services:      # Pflicht — definiert die Container
  ...

networks:      # Optional — definiert Netzwerke
  ...

volumes:       # Optional — definiert persistente Datenspeicher
  ...

configs:       # Optional — definiert Konfigurationsdateien
  ...

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:

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

Direktiven im services-Abschnitt

image

Das Docker-Image das für den Container verwendet wird.

services:
  nextcloud:
    image: lscr.io/linuxserver/nextcloud:latest

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.

services:
  nextcloud:
    container_name: nextcloud

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.

restart: unless-stopped
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:

services:
  mein-dienst:
    build:
      context: ./mein-verzeichnis   # Pfad zum Dockerfile
      dockerfile: Dockerfile.prod   # optional: anderer Dateiname

ports

Portweiterleitung zwischen Host und Container: HOST:CONTAINER.

services:
  caddy:
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"   # für HTTP/3

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.

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:                         # Docker verwaltet den Speicherort
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.

services:
  nextcloud:
    environment:
      - PUID=1000
      - PGID=1001
      - TZ=Europe/Berlin
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}   # Wert aus .env-Datei

Alternativ als Map-Schreibweise:

    environment:
      PUID: "1000"
      TZ: Europe/Berlin

env_file

Umgebungsvariablen aus einer externen Datei laden — sinnvoll für Passwörter:

services:
  nc-db:
    env_file:
      - .env

Die .env-Datei enthält dann:

MYSQL_ROOT_PASSWORD=sehrgeheim
MYSQL_DATABASE=nextcloud
MYSQL_USER=ncuser
MYSQL_PASSWORD=auchgeheim

Die .env-Datei gehört in .gitignore — niemals in ein öffentliches Repository einchecken.

networks

Netzwerkzugehörigkeit des Containers.

services:
  nextcloud:
    networks:
      mein_netzwerk:
        ipv4_address: 172.18.0.20   # feste IP (optional)

  collabora:
    networks: [mein_netzwerk]       # Kurzschreibweise ohne feste IP

networks:
  mein_netzwerk:
    external: true                  # Netzwerk existiert bereits

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.

services:
  nextcloud:
    extra_hosts:
      - "collabora.beispiel.de:172.18.0.6"

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.

services:
  nextcloud:
    depends_on:
      db:
        condition: service_healthy   # wartet bis Healthcheck grün ist
      redis:
        condition: service_healthy
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.

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

Das $$ ist kein Tippfehler — doppeltes Dollar-Zeichen verhindert dass Compose die Variable selbst auflöst; sie wird an die Shell im Container übergeben.

tmpfs

Flüchtige Dateisysteme die nur im RAM leben und beim Container-Stopp verschwinden.

services:
  nc-redis:
    tmpfs:
      - /data
      - /tmp
      - /run

  collabora:
    tmpfs:
      - /tmp
      - /var/tmp

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.

services:
  nextcloud:
    dns:
      - 1.1.1.1    # Cloudflare
      - 9.9.9.9    # Quad9
      - 8.8.8.8    # Google

command

Überschreibt den Standard-Startbefehl des Images.

services:
  nc-db:
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW

security_opt und cap_drop / cap_add

Container-Härtung: Linux-Capabilities entziehen oder hinzufügen.

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

Faustregel:

  • no-new-privileges: true — immer möglich, immer gut
  • cap_drop: ALL — wo immer es geht
  • read_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:

services:
  nc-redis:
    read_only: true
    tmpfs:
      - /data
      - /tmp
      - /run

Der networks-Abschnitt

Externes Netzwerk verwenden

networks:
  mein_netzwerk:
    external: true   # Netzwerk wurde bereits mit docker network create angelegt

Neues Netzwerk anlegen

networks:
  mein_netzwerk:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1

Der volumes-Abschnitt

Benanntes Volume

volumes:
  caddy_data:      # Docker verwaltet den Speicherort unter /var/lib/docker/volumes/
  caddy_config:

Volume mit tmpfs-Treiber (RAM-Disk)

volumes:
  cool-child-roots:
    driver_opts:
      type: tmpfs
      device: tmpfs
      o: "size=4g,mode=1777"

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
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 Alle Tabs durch Leerzeichen ersetzen
Address already in use Feste IP bereits von anderem Container belegt docker network inspect → Schuldigen finden
Container startet, Dienst funktioniert nicht depends_on ohne condition: service_healthy Healthcheck ergänzen
Umgebungsvariable leer .env liegt nicht im selben Verzeichnis Pfad prüfen oder env_file explizit angeben
$$VARIABLE wird nicht aufgelöst Gewolltes Verhalten Für Shell-Variablen im Container: $$. Für Compose-Variablen: $

Siehe auch