Docker hilft uns durch sogenannte Container dabei die technische Serverinfrastruktur, die für verschiedenste Projekte benötigt wird, schnell und vom Basissystem isoliert aufzusetzen.
Außerdem veringern wir durch den Einsatz von Docker den Wartungsaufwand
z.B. für Systemaktualsierungen und Backups. Tausende von Projekten stehen vorgefertigt zur Verfügung, die mit einem einfachen Kommando nur gestartet werden können. In diesem Artikel beschreibe ich wie Docker und das zusätzlich hilfreiche Docker Compose installiert werden. Docker Compose
hilft uns dabei ganze Containerlandschaften zur orchestrieren. Ein Beispiel:
Bei der Installation von WordPress über Docker werden mehrere Container benötigt, einer für die App und einer für die Datenbank. Wenn wir diese nun alle einzeln starten und evtl. dabei noch bestimmte Parameter übergeben wollen, wird es schnell uünbersichtlich.
Mit Compose können wir alle benötigten Parameter und Container in einer strukturierten Datei zusammenfassen und diese Umgebung mit einfachen Befehlen steuern.

Wir gehen davon aus, dass wir ein Linuxsystem auf Basis von Debian als Betriebssystem nutzen.
Das können z.B. das Raspberry Pi OS, Ubuntu oder eine der vielen anderen Varianten davon sein.
Grunsätzlich funktioniert Docker aber auch unter allen anderen Linux Systemen und auch unter Windows oder MacOS.
Wir wollen immer die aktuellsten Versionen nutzen und orientieren uns deshalb an den Anleitungen
auf den offiziellen Docker Seiten dazu.
Wenn man sich nicht von der augeinscheinlich großen Masse der Schritte erschlagen lässt,
sehen wir dass wir im Endeffekt nur 3-4 Kommandos per Copy & Pase in unsere Kommandozeile kopieren mussten.
Am besten führen wir abschließend noch einmalig einen Reboot unseres System durch.
Der Aufbau einer Docker-Composse Konfiguration ist im endeffeckt immer gleich und
basiert auf einer einzelnen Datei, der compose.yml.
Diese legt man am besten nicht einfach irgendwo hin, sondern überlegt sich eine Ordnerstruktur,
da man ja evtl. viele Umgebungen parallel betreiben und den Überblick behalten möchten.
Ein Beispiel:
Dieses Beispiel wollen wir noch etwas für uns anpassen und für den dauerhaften Betrieb optimieren.
Das kann dann so aussehen (Achtung die Formatierung mit den freien Zeichen als Tabs ist bindend):
services:
wordpress-app:
container_name: wordpress-app
hostname: wordpress-app
image: wordpress
restart: always
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: wordpress-db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
TZ: Europe/Berlin
volumes:
- ./wordpress:/var/www/html
logging:
options:
max-size: "10m"
max-file: "3"
wordpress-db:
container_name: wordpress-db
hostname: wordpress-db
image: mysql:5.7
restart: always
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: '1'
TZ: Europe/Berlin
volumes:
- ./db:/var/lib/mysql
logging:
options:
max-size: "10m"
max-file: "3"
Zur Erklärung:
Aus dem Verzeichnis heraus, in dem wir unsere compose.yml angelegt haben können wir unsere Umgebung
nun mit docker compose up -d starten.
Wenn wir alle Container aus der Compose Datei herunterfahren möchten,
geht das mit docker compose down. Mit dem Befehl docker compose pull ziehen wir die aktuellste
Version unserer Images von Docker Hub. Nach dem Pull ist ein erneutes docker compose up -d
erforderlich um die Container in der neuen Version zu starten.
Die persistenten Daten aus den definierten Volumes bleiben dabei natürlich erhalten.
Gefährlich wird es nur bei größeren Versionssprüngen innerhalb der Anwendung.
Dann gibt es entweder die Möglichkeit die Umgebung herunterzufahren,
das letzte geladene Image zu löschen und dann wieder zu starten.
Alternativ lässt sich in der compose.yml auch eine version des Images fest einstellen.
Damit verzichtet man in der Regel aber auch Sicherheitsupdates.
Warum um Himmels willen sollte man Containern feste IP-Adresse zuweisen?
Eine absolut berechtigte Frage auf die die Antwort lautet: Am besten nie!
Denn: Docker kommt mit einer Art eingebauten DNS- und DHCP-Server und verwaltet die IP-Adresse wunderbar selbst. Leider gibt es trotzdem Spezialfälle in denen man sich das Leben vereinfachen kann, wenn man IPs händisch setzt.
So ein Spezialfall wäre z.B. gegeben, wenn die Applikation die in einem Container betrieben wird nicht mit Hostnamen sondern nur mit IP-Adressen umgehen kann, um auf einen weiteren Container zuzugreifen.
Früher war das ein relativ kompliziertes unterfangen, mit den aktuellen Docker (Compose) Versionen ist aber auch das kinderleicht.

Falls du bereits diverse Container innerhalb eines Netzes am laufen hast,
wäre es gefährlich nur einigen davon eine feste IP-Adresse zuzuordnen.
Warum? Weil, es dir dann z.B. nach einem Reboot deines Servers passieren könnte,
dass sich die automatisch zugewiesene IP eines Containers mit einer von dir fest
zugeordneten eines anderen Containers überschneidet. Das Resultat wäre,
dass der zweite Container nicht starten kann, da die IP ja bereits vergeben ist.
Um zu erfahren welche IP-Adresse einem Container aktuell zugewiesen ist, hilft uns das Kommando
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' CONTAINERNAMEoderID
Falls man die IP-Adressen aller laufenden Container herausfinden möchte, ist dieses Kommando sehr nützlich:
docker inspect $(docker ps -q ) --format='{{ printf "%-50s" .Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}'
In unserem Beispiel wollen wir allen Containern die in einem für unseren Caddy Reverse Proxy erstellen
Netz liegen feste IP-Adressen vergeben. Falls ihr noch kein solches Netz habt, könnt ihr dieses über den Befehl
docker network create --subnet=172.28.0.0/16 proxy
anlegen. Nachdem dies erfolgt ist, können wir nun in den Docker Compose Definitionen der
einzelnen Container die IP-Adressen 172.20.0.X verwenden. Wobei X für die Zahlen 1-255 steht.
Beginnen wir mit der Zuweisung einer festen IP für unseren Caddy Reverse Proxy:
services:
caddy:
container_name: caddy
image: caddy:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./site:/srv
- ./caddy_data:/data
- ./caddy_config:/config
logging:
options:
max-size: "10m"
max-file: "3"
networks:
proxy:
ipv4_address: 172.28.0.1
volumes:
caddy_data:
caddy_config:
networks:
proxy:
external: true
Nach dem speichern und anschließendem Neustart der Umgebung, ist die IP fest zugeordnet.
Entscheidend sind die Blöcke:
networks:
proxy:
ipv4_address: 172.28.0.1
sowie
networks:
proxy:
external: true
Nun verfahren wir genauso mit allen anderen Containern mit einer jeweils individuellen IP
und haben unser vorhaben erfolgreich beendet.
Auch wenn die Zuweisung fester IP-Adressen relativ einfach ist, erscheint das Ganze als etwas „unsauber“.
So widerspricht die Vorgehensweise doch dem Prinzip,
dass ich die Definition eines Docker Containers nehmen und jederzeit auf einem anderen Server/Host
starten kann. Das ist so nun nicht mehr möglich, weil zunächst manuell ein neues Netz angelegt werden muss.
Anderseits ist die manuelle Erstellung des Netzes sowieso erforderlich,
wenn man eine Art DMZ (eigenes Netz) für seinen Reverse Proxy definiert.
Nur der manuell vergebene Subnetzbereich ist hier der zusätzliche Aufwand.
So bleibt die Verwendung fester IP-Adressen als ein Spezialfall der nur in bestimmten Szenarien Sinn macht.