Modul 9 · 9.3
Deutsch
Docker compose und die Firewall
Modul 9: Docker und docker compose · Lesezeit: 12 Minuten
Lernziel: Nach dieser Lektion können Sie Dienste in einer Compose-Datei beschreiben, sie starten, aktualisieren und stoppen. Außerdem können Sie erklären, warum veröffentlichte Ports von Docker an ufw vorbeigehen und wie Sie das verhindern.
Kernbegriffe: compose.yaml, services, image, ports, volumes, environment, .env, restart, networks, depends_on, healthcheck, docker compose up -d, pull, logs, down, veraltetes version:, Docker und ufw, DOCKER-USER, Veröffentlichen auf 127.0.0.1, Geheimnisse, docker.sock
Warum Compose?
Mit docker run (Lektion 9.2) starten Sie einen einzelnen Container mit einer langen Zeile voller Optionen. Eine echte Anwendung besteht oft aus mehreren Containern: einem Programm, einer Datenbank, einem Reverse Proxy. Diese Zeilen im Kopf zu behalten und nach einem Neustart oder Update erneut einzutippen, geht irgendwann schief.
Docker Compose beschreibt alle Container einer Anwendung in einer einzigen Datei: compose.yaml. Sie legen diese Datei in einem eigenen Verzeichnis ab, zum Beispiel /srv/status/compose.yaml, und verwalten alles mit docker compose. Die Datei ist zugleich Ihre Dokumentation: Was läuft, welches Image, welche Ports, wo liegen die Daten. Nehmen Sie dieses Verzeichnis in Ihr Backup auf (Lektion 8.1), dann können Sie die Anwendung auf einem neuen Server in wenigen Minuten wiederherstellen.
Das Plug-in heißt docker compose (mit Leerzeichen, Version 2). Das alte eigenständige Programm docker-compose (mit Bindestrich) wurde ersetzt; Sie finden es noch in älteren Anleitungen. Auch das Feld version: "3.8" am Anfang älterer Dateien ist veraltet: Compose verwendet immer das neueste Schema und gibt bei version: nur eine Warnung aus.
Eine Compose-Datei lesen
Ein Beispiel mit einem Programm und einer PostgreSQL-Datenbank:
# /srv/notities/compose.yaml
services:
app:
image: ghcr.io/example/notities:1.4
restart: unless-stopped
ports:
- "127.0.0.1:8081:8080"
environment:
DB_HOST: db
DB_USER: notities
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: notities
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U notities"]
interval: 10s
retries: 5
volumes:
db-data:Schritt für Schritt:
- services: Jeder Dienst wird zu einem Container. Der Name (
app,db) ist zugleich der Hostname, über den sich die Container gegenseitig finden. Compose legt für die Anwendung ein eigenes Netzwerk an, daher verbindet sichappmit der Datenbank unterdb:5432. - image: immer mit einem Versions-Tag (Lektion 9.1).
- restart: was Docker tut, wenn der Container stoppt oder der Server neu startet.
unless-stoppedstartet ihn wieder, es sei denn, Sie haben ihn bewusst gestoppt. Ohne Restart-Richtlinie kommt Ihre Anwendung nach einem Neustart (Lektion 4.2) nicht zurück. - ports: Nur die App veröffentlicht einen Port, und zwar nur auf
127.0.0.1. Die Datenbank veröffentlicht nichts: Die App erreicht sie über das interne Netzwerk. Eine Datenbank mitports: - "5432:5432"ist einer der häufigsten Fehler (siehe unten). - environment: Einstellungen für das Programm. Das Passwort steht dort nicht im Klartext, sondern als
${DB_PASSWORD}. - volumes: Das benannte Volume
db-databewahrt die Datenbank auf, wenn der Container ersetzt wird. - depends_on mit healthcheck: Die App startet erst, wenn die Datenbank wirklich bereit ist, nicht nur gestartet.
Geheimnisse in einer .env-Datei
Compose liest Variablen wie ${DB_PASSWORD} aus einer Datei .env im selben Verzeichnis:
# /srv/notities/.env
DB_PASSWORD=Kies-hier-een-lang-willekeurig-wachtwoordanna@web1:/srv/notities$ sudo chmod 600 .envSo steht das Passwort nicht in der Compose-Datei, die Sie vielleicht in Git speichern oder in einem Forum einfügen, um Hilfe zu bekommen. Tragen Sie .env in .gitignore ein. Erzeugen Sie das Passwort mit einem Passwortmanager oder mit openssl rand -base64 24.
Tägliche Nutzung
Alle Befehle führen Sie in dem Verzeichnis mit der Compose-Datei aus:
anna@web1:/srv/notities$ sudo docker compose config # Datei prüfen, Variablen eingesetzt
anna@web1:/srv/notities$ sudo docker compose up -d # alles starten (oder auf Stand der Datei bringen)
anna@web1:/srv/notities$ sudo docker compose ps # Status pro Dienst
anna@web1:/srv/notities$ sudo docker compose logs -f app
anna@web1:/srv/notities$ sudo docker compose pull # neue Images holen
anna@web1:/srv/notities$ sudo docker compose up -d # Container durch die neuen Images ersetzen
anna@web1:/srv/notities$ sudo docker compose down # stoppen und Container entfernendown lässt Ihre Volumes stehen. down -v entfernt auch die Volumes der Anwendung und damit Ihre Datenbank. Geben Sie das nur ein, wenn Sie wirklich von vorn beginnen möchten.
docker compose config ist das Gegenstück zu nginx -t und sshd -t: Es zeigt, wie Compose Ihre Datei versteht, mit allen eingesetzten Variablen, bevor Sie etwas starten. Achten Sie darauf, diese Ausgabe nirgendwo einzufügen, denn die Passwörter stehen darin.
Docker geht an ufw vorbei
Nun zum wichtigsten Sicherheitspunkt dieses Moduls. In Lektion 5.2 haben Sie ufw auf "alles ablehnen, außer SSH, 80 und 443" eingestellt. Sie würden erwarten, dass ein Container mit ports: - "5432:5432" dann nicht erreichbar ist. Das stimmt nicht.
Die Docker-Dokumentation sagt es ausdrücklich: Docker und ufw verwenden Firewallregeln auf eine Weise, die nicht miteinander vereinbar ist. Wenn Sie einen Port veröffentlichen, trägt Docker selbst Regeln in die Firewall des Kernels ein. Diese Regeln leiten den Verkehr an den Container weiter (Adressumsetzung, DNAT), bevor die Regeln von ufw angewendet werden. Das Paket geht also an ufw vorbei. ufw status zeigt nichts Auffälliges, während der Port aus dem gesamten Internet erreichbar ist. Dasselbe gilt für firewalld.
Außerdem veröffentlicht Docker ohne Adressangabe auf 0.0.0.0 und [::], also über IPv4 und IPv6 (Lektion 9.1).
Drei Wege, es richtig zu machen, von einfach bis anspruchsvoll:
- Veröffentlichen Sie nichts, was nicht öffentlich sein muss. Datenbanken, Caches und interne Dienste bekommen kein
ports:. Container in derselben Compose-Anwendung erreichen sich über das interne Netzwerk. - Veröffentlichen Sie auf Loopback.
"127.0.0.1:8081:8080". Dann kann nur der Server selbst darauf zugreifen, und Sie setzen einen Reverse Proxy (nginx oder Caddy, Modul 7) davor, der auf 443 lauscht. Docker lässt sich auch standardmäßig so einrichten: Eine Daemon-Einstellung sorgt dafür, dass alle veröffentlichten Ports ohne Adressangabe auf127.0.0.1landen. - Regeln in der Kette DOCKER-USER. Docker legt eine leere Kette
DOCKER-USERan, die vor seinen eigenen Regeln durchlaufen wird. Dort können Sie eigene Regeln eintragen, zum Beispiel "nur diese IP-Adresse darf auf die veröffentlichten Ports zugreifen". Das ist fortgeschrittene Arbeit mit iptables oder nftables, bei der sich leicht Fehler einschleichen. Für die meisten kleinen Server reichen 1 und 2 aus.
Prüfen Sie immer von außen, wie in Lektion 5.1: nc -vz -w 3 <ip> 5432 von Ihrem Laptop aus oder mit der Kursprüfung der Aufgaben Z5.1 und Z10.4.
Zwei weitere Dinge, die Sie nicht tun sollten
- Kein
/var/run/docker.sockin einen Container mounten, es sei denn, Sie wissen genau, warum. Ein Container mit Zugriff auf den Docker-Socket kann selbst Container starten, auch einen mit der gesamten Festplatte des Hosts (Lektion 9.2). Manche Hilfsprogramme (Dashboards, automatische Updater) verlangen danach; das bedeutet im Grunde, diesem Programm Root-Rechte zu geben. - Kein
privileged: trueodernetwork_mode: host, nur weil eine Anleitung es sagt. Beides hebt die Abschirmung des Containers weitgehend auf. Nur verwenden, wenn der Hersteller erklärt, warum es nötig ist.
Zusammenfassung
- Eine Compose-Datei (
compose.yaml) beschreibt alle Container einer Anwendung; sie ist zugleich Dokumentation und gehört in Ihr Backup. - Verwenden Sie Versions-Tags, eine Restart-Richtlinie, benannte Volumes, wo möglich einen Healthcheck und Geheimnisse in einer
.envmit Rechten 600. docker compose config,up -d,ps,logs,pull,down;down -ventfernt auch die Volumes.- Veröffentlichte Ports gehen an ufw und firewalld vorbei. Veröffentlichen Sie nur, was öffentlich sein muss, und sonst auf
127.0.0.1mit einem Reverse Proxy davor. - Kein docker.sock,
privilegedodernetwork_mode: hostohne guten Grund.
Quiz
Aufgabe
Aufgabe Z9.3: Eine Compose-Datei prüfen. In der Beispieldatei compose-nakijken.txt steht (als Textdatei) die Compose-Datei eines kleinen Webshops mit einer App, einer Datenbank, einem Cache und einem Verwaltungstool. Sie enthält sechs Fehler, die in dieser Lektion oder in den Lektionen 9.1 und 9.2 behandelt wurden. Geben Sie für jeden nummerierten Zeilenblock an, ob er richtig oder falsch ist, und wählen Sie bei jedem Fehler die richtige Korrektur. Das Programm vergleicht mit dem festen Lösungsschlüssel. Wenn Sie eine eigene Compose-Datei haben, prüfen Sie diese anschließend anhand derselben Punkte.
Weiterführende Informationen
- Docker-Dokumentation, How Compose works (compose.yaml): https://docs.docker.com/compose/intro/compose-application-model/
- Docker-Dokumentation, Version and name top-level elements: https://docs.docker.com/reference/compose-file/version-and-name/
- Docker-Dokumentation, Packet filtering and firewalls (Docker and ufw): https://docs.docker.com/engine/network/packet-filtering-firewalls/
- Docker-Dokumentation, Port publishing and mapping: https://docs.docker.com/engine/network/port-publishing/
- Docker-Dokumentation, Interpolation (Variablen und .env): https://docs.docker.com/reference/compose-file/interpolation/
