Docker Essentials

Aus Xinux Wiki
Version vom 6. September 2026, 08:43 Uhr von Thomas.will (Diskussion | Beiträge)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springen Zur Suche springen

Docker – Grundlagen, Images, Container und Compose

Einführung

Was ist ein Container

Definition

Ein Container ist ein Prozess auf dem Host-System, der durch Kernel-Funktionen von den übrigen Prozessen abgeschottet wird. Er bringt sein eigenes Dateisystem, seinen eigenen Netzwerk-Stack und seinen eigenen Prozessbaum mit, benutzt aber den Kernel des Hosts.

Was ein Container mitbringt
  • die Anwendung selbst
  • die Laufzeitumgebung (z.B. Python, Node.js, Java)
  • die benötigten Bibliotheken und Werkzeuge
  • die Konfigurationsdateien
  • die Umgebungsvariablen
Die zwei Kernel-Mechanismen dahinter
Mechanismus Aufgabe
Namespaces Trennt Sichtbarkeit: eigener Prozessbaum, eigenes Netzwerk, eigene Mountpoints, eigene Benutzer-IDs
cgroups Begrenzt Verbrauch: CPU, Arbeitsspeicher, Block-I/O

Die Ausgangslage ohne Container

"Auf meinem Rechner läuft es"

Eine Anwendung wird auf dem Arbeitsplatz entwickelt und getestet, auf dem Server läuft sie nicht. Ursache sind Unterschiede in der Betriebssystemversion, in den Versionen der installierten Software, in den Bibliotheken und in der Konfiguration.

Abhängigkeitskonflikte

Zwei Anwendungen auf demselben Server brauchen dieselbe Bibliothek in unterschiedlichen Versionen. Installiert werden kann nur eine davon.

Ressourcenverbrauch

Der klassische Ausweg war eine virtuelle Maschine je Anwendung. Jede davon bringt ein vollständiges Betriebssystem mit und belegt Arbeitsspeicher, Plattenplatz und CPU-Zeit, obwohl nur ein einzelner Dienst darin läuft.

Trennung von Diensten

Auf einem gemeinsam genutzten Server beeinflussen sich Dienste gegenseitig. Ein Update für den einen kann den anderen stillegen.

Container und virtuelle Maschine

Virtuelle Maschine

Ein Hypervisor stellt virtuelle Hardware bereit. Darauf läuft ein vollständiges Betriebssystem mit eigenem Kernel.

Container

Die Container-Laufzeit startet einen isolierten Prozess auf dem vorhandenen Kernel. Es wird kein zweites Betriebssystem gebootet.

Virtuelle Maschine Container
Kernel eigener Kernel je VM gemeinsamer Kernel des Hosts
Startzeit Sekunden bis Minuten Millisekunden
Größe mehrere Gigabyte wenige Megabyte bis wenige hundert Megabyte
Isolation durch Virtualisierung, sehr stark durch Kernel-Funktionen, schwächer
Fremde Kernel möglich (Windows auf Linux-Host) nicht möglich
Aufbau im Vergleich
Virtuelle Maschine              Container
+---------------------+        +---------------------+
|     Anwendung       |        |     Anwendung       |
+---------------------+        +---------------------+
|      Gast-OS        |        |  Container-Laufzeit |
+---------------------+        +---------------------+
|     Hypervisor      |        |                     |
+---------------------+        |      Host-OS        |
|      Host-OS        |        |                     |
+---------------------+        +---------------------+
|      Hardware       |        |      Hardware       |
+---------------------+        +---------------------+
Konsequenz für die Praxis

Auf einem Linux-Host laufen Container mit Debian, Ubuntu, Alpine oder Rocky Linux nebeneinander, weil nur die Userland-Dateien unterschiedlich sind. Ein Windows-Container läuft auf einem Linux-Host nicht.

Aufbau von Docker

Komponente Aufgabe
Docker-Client (docker) Kommandozeilenwerkzeug, nimmt die Befehle entgegen und schickt sie an den Daemon
Docker-Daemon (dockerd) Dienst auf dem Host, verwaltet Images, Container, Netzwerke und Volumes
containerd / runc Startet und überwacht die eigentlichen Container-Prozesse
Registry Server, auf dem Images liegen (Docker Hub oder eine eigene Registry)
Zusammenspiel
Benutzer --> docker --> dockerd --> containerd --> Container
                          |
                          v
                       Registry
Kommunikationsweg

Der Client spricht den Daemon über den Unix-Socket /var/run/docker.sock an. Wer auf diesen Socket schreiben darf, hat vollen Zugriff auf den Host.

Begriffe

Begriff Bedeutung
Image Unveränderliche Vorlage, aus der Container erzeugt werden
Container Laufende Instanz eines Images
Dockerfile Textdatei mit der Bauanleitung für ein Image
Registry Ablage für Images
Volume Von Docker verwalteter Speicherbereich für dauerhafte Daten
Compose Werkzeug, um mehrere Container aus einer YAML-Datei gemeinsam zu betreiben

Übung

  • Zeichnen Sie auf dem Blatt zwei Stapel nebeneinander: einmal Hardware → Host-OS → Hypervisor → Gast-OS → Anwendung, einmal Hardware → Host-OS → Container-Laufzeit → Anwendung.
  • Notieren Sie zu jeder der folgenden Anforderungen, ob Sie eine VM oder einen Container wählen würden, und warum: eigener Kernel mit eigenem Modul, 30 gleichartige Webdienste auf einem Host, Windows-Server-Dienst auf einem Debian-Host, Testumgebung die in Sekunden neu aufgebaut werden muss.

Installation unter Debian

Ausgangslage

Alle Übungen laufen auf einer Debian-VM in VirtualBox. Die Pakete aus dem Debian-Repository (docker.io) hinken der Entwicklung hinterher und enthalten das Compose-Plugin nicht. Deshalb wird das offizielle Docker-Repository eingebunden.

Paketquelle einbinden

Grundpakete installieren
  • sudo apt update
  • sudo apt install -y ca-certificates curl
Angabe Bedeutung
-y Rückfragen werden automatisch mit Ja beantwortet
ca-certificates Wurzelzertifikate, damit HTTPS-Downloads geprüft werden können
curl Werkzeug zum Herunterladen des Signaturschlüssels
Verzeichnis für den Signaturschlüssel anlegen
  • sudo install -m 0755 -d /etc/apt/keyrings
Angabe Bedeutung
-m 0755 Rechte des angelegten Verzeichnisses
-d Es wird ein Verzeichnis statt einer Datei angelegt
Signaturschlüssel holen
Angabe Bedeutung
-f Bei HTTP-Fehlern keine Fehlerseite speichern, sondern abbrechen
-s Keine Fortschrittsanzeige
-S Fehlermeldungen trotz -s anzeigen
-L Weiterleitungen folgen
-o Zieldatei
a+r Leserecht für alle, damit apt den Schlüssel lesen kann
Hinweis

apt-key gibt es nicht mehr. Schlüssel liegen heute als Datei unter /etc/apt/keyrings und werden in der Paketquelle über signed-by genau dieser einen Quelle zugeordnet.

Paketquelle eintragen
  • vim /etc/apt/sources.list.d/docker.list
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian trixie stable
Angabe Bedeutung
arch=amd64 Nur die 64-Bit-Intel/AMD-Architektur wird geladen
signed-by=… Nur mit diesem Schlüssel signierte Pakete werden aus dieser Quelle akzeptiert
trixie Codename der Debian-Version; bei einer anderen Debian-Version hier anpassen
stable Der stabile Zweig des Docker-Repositories

Docker installieren

  • sudo apt update
  • sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Paket Inhalt
docker-ce Der Daemon
docker-ce-cli Das Kommando docker
containerd.io Die darunterliegende Container-Laufzeit
docker-buildx-plugin Der aktuelle Build-Unterbau (BuildKit)
docker-compose-plugin Das Unterkommando docker compose

Installation prüfen

Version des Clients und des Daemons
  • docker version
Dienst-Status
  • systemctl status docker
Systemweite Informationen
  • docker info
Funktionstest
  • sudo docker run --rm hello-world
Angabe Bedeutung
--rm Der Container wird nach dem Ende automatisch gelöscht

Docker ohne sudo benutzen

  • sudo usermod -aG docker $USER
  • newgrp docker
Angabe Bedeutung
-a Gruppe hinzufügen, bestehende Gruppen bleiben erhalten
-G Betrifft die Nebengruppen
newgrp docker Die neue Gruppe gilt sofort in dieser Sitzung, ohne Neuanmeldung
Sicherheitshinweis

Mitglieder der Gruppe docker dürfen den Docker-Socket ansprechen und darüber Container mit vollen Hostrechten starten. Diese Gruppenmitgliedschaft ist gleichwertig mit Root-Rechten auf dem Host.

Aufbau der Kommandozeile

Grundmuster
  • docker [Objekt] [Aktion] [Optionen] [Argumente]
Beispiel
  • docker container ls -a
Kurzformen

Häufige Kommandos gibt es zusätzlich ohne Objekt: docker ps statt docker container ls, docker images statt docker image ls.

Übung

  • Installieren Sie Docker auf Ihrer Debian-VM über das offizielle Repository.
  • Prüfen Sie mit docker version, dass Client und Server dieselbe Version melden.
  • Nehmen Sie Ihren Benutzer in die Gruppe docker auf und starten Sie docker run --rm hello-world ohne sudo.
  • Lassen Sie sich mit docker info anzeigen, welcher Storage-Treiber verwendet wird.

Images

Was ist ein Image

Ein Image ist eine unveränderliche Vorlage. Es enthält ein fertiges Dateisystem und die Angabe, welches Kommando beim Start ausgeführt wird. Aus einem Image lassen sich beliebig viele Container erzeugen.

Beim Start eines Containers legt Docker über die schreibgeschützten Image-Schichten eine dünne beschreibbare Schicht. Alle Änderungen im laufenden Container landen dort und sind mit dem Container weg.

Layer

Ein Image besteht aus übereinanderliegenden Schichten. Jede Anweisung im Dockerfile erzeugt eine Schicht.

Schicht Inhalt
1 Debian-Basis
2 Systempakete nachinstalliert
3 Python-Abhängigkeiten installiert
4 Anwendungscode kopiert
Vorteile
  • Beim Neubau werden nur die Schichten ab der geänderten Anweisung neu erzeugt, der Rest kommt aus dem Cache
  • Mehrere Images teilen sich gemeinsame Basisschichten
  • Jede Schicht liegt nur einmal auf der Platte

Overlay-Dateisystem

Docker legt die Schichten mit overlay2 übereinander. Sichtbar ist immer die oberste Fassung einer Datei. Wird eine Datei aus einer unteren Schicht geändert, kopiert der Kernel sie zuerst in die beschreibbare Schicht (Copy-on-Write) und ändert erst dann die Kopie.

Registries und Namensformat

Vollständiger Name eines Images
  • [Registry/][Namensraum/]Repository[:Tag]
Angabe Beispiel Bedeutung
Registry docker.io Server; fehlt die Angabe, wird Docker Hub benutzt
Namensraum library Konto oder Organisation; library steht für die offiziellen Images
Repository debian Name des Images
Tag 13 Version; fehlt die Angabe, wird latest benutzt
Gleichbedeutend
  • debian:13
  • docker.io/library/debian:13
Hinweis zu latest

latest ist kein Versionshinweis, sondern nur ein Tag-Name wie jeder andere. In Kursumgebungen und im Betrieb immer eine feste Version angeben.

Images herunterladen

  • docker pull debian:13
  • docker pull caddy:2

Images auflisten

  • docker images
Ausgabe
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        1.29      3b4a1c2f9e70   2 weeks ago    192MB
debian       13        7c9e5d1a4b83   3 weeks ago    117MB
Spalte Bedeutung
REPOSITORY Name des Images
TAG Version
IMAGE ID Eindeutige Kennung
CREATED Zeitpunkt, an dem das Image gebaut wurde
SIZE Größe
Ausgabe auf bestimmte Spalten begrenzen
Angabe Bedeutung
--format Eigene Ausgabevorlage; die Feldnamen in geschweiften Klammern werden ersetzt

Images untersuchen

Alle Metadaten als JSON
  • docker inspect debian:13
Nur einzelne Felder
Bauschritte eines Images anzeigen
  • docker history caddy:2

Images löschen

Ein bestimmtes Image
  • docker rmi debian:13
Alle Images, die von keinem Container benutzt werden
  • docker image prune -a
Angabe Bedeutung
-a Nicht nur namenlose Zwischenschichten, sondern alle unbenutzten Images

Übung

  • Laden Sie debian:13 und alpine:3 herunter und vergleichen Sie die Größen.
  • Lassen Sie sich mit docker history die Schichten von caddy:2 anzeigen und bestimmen Sie die größte Schicht.
  • Geben Sie mit docker inspect --format nur das Kommando aus, das beim Start des Caddy-Images ausgeführt wird.
  • Löschen Sie alpine:3 wieder.

Container

Lifecycle

Zustand Bedeutung
created Container angelegt, aber nicht gestartet
running Prozess läuft
paused Prozesse angehalten, Speicher bleibt belegt
exited Prozess beendet, Container samt Daten noch vorhanden
removed Container gelöscht
Wichtig

Ein Container lebt genau so lange wie sein Hauptprozess. Endet der Prozess, endet der Container.

Ersten Container starten

  • docker run --name web -d -p 8080:80 nginx:1.29
Angabe Bedeutung
--name web Fester Name; ohne diese Angabe vergibt Docker einen Zufallsnamen
-d Betrieb im Hintergrund, die Konsole bleibt frei
-p 8080:80 Port 8080 des Hosts wird auf Port 80 im Container geleitet
nginx:1.29 Zu verwendendes Image

Interaktiver Betrieb

  • docker run -it --rm debian:13 bash
Angabe Bedeutung
-i Standardeingabe bleibt offen
-t Ein Terminal wird zugeteilt
--rm Container wird nach dem Beenden gelöscht
bash Ersetzt das im Image hinterlegte Startkommando

Container auflisten

Laufende Container
  • docker ps
Ausgabe
CONTAINER ID   IMAGE        COMMAND                  STATUS         PORTS                                     NAMES
9f31c07ab2d4   nginx:1.29   "/docker-entrypoint.…"   Up 4 minutes   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   web
Alle Container, auch beendete
  • docker ps -a
Nur die Kennungen
  • docker ps -q
Angabe Bedeutung
-a Auch Container in anderen Zuständen als running
-q Nur die Container-IDs, geeignet zur Weiterverarbeitung

Starten, Stoppen, Neustarten

  • docker stop web
  • docker start web
  • docker restart web
Ablauf beim Stoppen

Docker schickt SIGTERM, wartet standardmäßig zehn Sekunden und schickt danach SIGKILL.

Wartezeit ändern
  • docker stop -t 30 web
Angabe Bedeutung
-t 30 Wartezeit in Sekunden bis zum harten Abbruch
Prozesse anhalten und fortsetzen
  • docker pause web
  • docker unpause web

Beim Anhalten werden die Prozesse im Container eingefroren, der belegte Arbeitsspeicher bleibt bestehen. Im Unterschied zu docker stop läuft der Container danach an derselben Stelle weiter.

Restart-Policy

  • docker run -d --name web --restart unless-stopped -p 8080:80 nginx:1.29
Wert Verhalten
no Kein automatischer Neustart (Vorgabe)
on-failure Neustart nur bei Exit-Code ungleich 0
always Immer, auch nach einem docker stop mit anschließendem Neustart des Daemons
unless-stopped Wie always, aber ein manuell gestoppter Container bleibt gestoppt

Container löschen

Beendeten Container löschen
  • docker rm web
Laufenden Container löschen
  • docker rm -f web
Alle beendeten Container löschen
  • docker container prune
Angabe Bedeutung
-f Container wird zuerst hart beendet und dann gelöscht

Logs

  • docker logs web
  • docker logs -f web
  • docker logs --tail 20 web
  • docker logs -t web
Angabe Bedeutung
-f Ausgabe läuft weiter mit
--tail 20 Nur die letzten 20 Zeilen
-t Mit Zeitstempel
Woher die Logs kommen

Docker sammelt das, was der Hauptprozess auf die Standardausgabe und die Standardfehlerausgabe schreibt. Was ein Dienst in eine Datei im Container schreibt, taucht hier nicht auf.

Kommandos im laufenden Container

  • docker exec web ls -l /usr/share/nginx/html
  • docker exec -it web bash
Angabe Bedeutung
-i Standardeingabe offen halten
-t Terminal zuteilen
Hinweis

Änderungen über docker exec liegen nur in der beschreibbaren Schicht dieses Containers und sind nach dem Löschen weg. Für dauerhafte Änderungen gehört die Anpassung ins Dockerfile.

Ressourcen begrenzen

  • docker run -d --name web --memory 256m --cpus 0.5 nginx:1.29
Angabe Bedeutung
--memory 256m Obergrenze für den Arbeitsspeicher
--cpus 0.5 Anteil an einem CPU-Kern
Laufende Auslastung anzeigen
  • docker stats

Übung

  • Starten Sie einen Nginx-Container im Hintergrund mit dem Namen web und Portweiterleitung 8080 auf 80.
  • Rufen Sie die Seite mit curl http://localhost:8080 ab und sehen Sie sich anschließend die Logs an.
  • Öffnen Sie mit docker exec eine Shell im Container und ersetzen Sie die Datei /usr/share/nginx/html/index.html durch einen eigenen Text.
  • Löschen Sie den Container, starten Sie ihn neu und prüfen Sie, was aus Ihrer Änderung geworden ist.
  • Starten Sie den Container erneut mit --restart unless-stopped, starten Sie die VM neu und prüfen Sie, ob der Container wieder läuft.

Ports veröffentlichen

Netzwerk-Namespace

Jeder Container bekommt einen eigenen Netzwerk-Namespace mit eigener IP-Adresse. Ein Dienst, der im Container auf Port 80 lauscht, ist damit zunächst nur innerhalb des Containers erreichbar.

Host-Port und Container-Port

Host-Port Richtung Container-Port
8080 80

Alles, was auf dem Host an Port 8080 ankommt, wird an Port 80 im Container weitergereicht.

Client ---> Host:8080 ---> Container:80 ---> Dienst

Port veröffentlichen

  • docker run -d -p 8080:80 nginx:1.29
Aufbau der Angabe
  • -p Host-Port:Container-Port

Mehrere Ports

  • docker run -d -p 8080:80 -p 8443:443 nginx:1.29

Bindung an eine bestimmte Adresse

  • docker run -d -p 127.0.0.1:8080:80 nginx:1.29
Wirkung

Der Dienst ist nur noch vom Host selbst erreichbar. Ohne Adressangabe bindet Docker an alle Adressen des Hosts.

Wichtig für die Firewall

Veröffentlichte Ports werden von Docker über eigene nftables-Regeln freigeschaltet. Eine Regel in der INPUT-Kette des Hosts greift dabei nicht. Wer den Zugriff einschränken will, bindet an eine feste Adresse wie oben oder arbeitet mit der Kette DOCKER-USER.

Zufällige Ports

  • docker run -d -P nginx:1.29
  • docker ps
Angabe Bedeutung
-P Alle im Image mit EXPOSE angegebenen Ports werden auf freie hohe Host-Ports gelegt

Die tatsächlich vergebenen Ports stehen in der Spalte PORTS der Ausgabe von docker ps.

PORTS
0.0.0.0:32768->80/tcp

Übung

  • Starten Sie zwei Nginx-Container gleichzeitig, einen auf Host-Port 8080, einen auf 8081.
  • Versuchen Sie, einen dritten Container ebenfalls auf 8080 zu starten, und lesen Sie die Fehlermeldung.
  • Starten Sie einen Container mit -p 127.0.0.1:8082:80 und prüfen Sie von einem zweiten Rechner aus, ob er erreichbar ist.

Daten dauerhaft speichern

Das Problem

Die beschreibbare Schicht gehört zum Container. Wird der Container gelöscht, sind die Daten weg. Für Datenbanken, Konfigurationen und Nutzdaten braucht es Speicher außerhalb des Containers.

Speicherarten

Art Ablage Einsatz
Bind Mount Beliebiges Verzeichnis des Hosts Konfigurationsdateien, Zertifikate, Entwicklung
Named Volume Von Docker verwaltet unter /var/lib/docker/volumes Nutzdaten von Diensten und Datenbanken
Anonymes Volume Wie Named Volume, aber ohne Namen Entsteht automatisch, ungeeignet für geplante Datenhaltung

Bind Mount

  • docker run -d -p 8080:80 -v /srv/web:/usr/share/nginx/html nginx:1.29
Aufbau der Angabe
  • -v Host-Verzeichnis:Container-Verzeichnis
Eigenschaft Bewertung
Direkter Zugriff vom Host Vorteil bei Konfiguration und Entwicklung
Abhängig von der Verzeichnisstruktur des Hosts Nachteil bei Umzug auf ein anderes System
Rechte richten sich nach den Host-Rechten Häufige Fehlerquelle bei "permission denied"

Named Volume

Volume anlegen
  • docker volume create webdata
Volume verwenden
  • docker run -d -v webdata:/usr/share/nginx/html nginx:1.29
Aufbau der Angabe
  • -v Volumename:Container-Verzeichnis
Verhalten beim ersten Start

Ist das Volume leer, kopiert Docker den Inhalt des Container-Verzeichnisses aus dem Image hinein. Bei einem Bind Mount passiert das nicht, dort überdeckt das Host-Verzeichnis den Inhalt.

Anonymes Volume

Enthält ein Image die Anweisung VOLUME, legt Docker beim Start ein Volume ohne Namen an. Solche Volumes tauchen in docker volume ls nur mit einer langen Kennung auf und bleiben nach dem Löschen des Containers zurück.

  • vim Dockerfile
FROM debian:13
VOLUME /app/data
CMD ["bash"]
  • docker build -t volumetest .
  • docker run -it --rm volumetest
  • docker volume ls

Volume in mehreren Containern

Ein Named Volume kann von mehreren Containern gleichzeitig eingebunden werden.

  • docker run -d --name schreiber -v gemeinsam:/app/data debian:13 sleep 3600
  • docker run -d --name leser -v gemeinsam:/app/data debian:13 sleep 3600
  • docker exec schreiber sh -c 'echo Testeintrag > /app/data/test.txt'
  • docker exec leser cat /app/data/test.txt
Angabe Bedeutung
sleep 3600 Hält den Container am Leben, damit er für docker exec zur Verfügung steht
sh -c Das Kommando wird in einer Shell ausgeführt, damit die Umleitung > im Container wirkt und nicht auf dem Host
Hinweis

Docker regelt den gleichzeitigen Zugriff nicht. Schreiben mehrere Container in dieselben Dateien, muss die Anwendung selbst dafür sorgen, dass sich das verträgt.

Volumes verwalten

  • docker volume ls
  • docker volume inspect webdata
  • docker volume rm webdata
  • docker volume prune
Kommando Bedeutung
ls Alle Volumes anzeigen
inspect Ablageort und Optionen anzeigen
rm Einzelnes Volume löschen
prune Alle Volumes löschen, die kein Container benutzt

Daten sichern und zurückspielen

Sichern
  • docker run --rm -v webdata:/source -v /backup:/backup debian:13 tar czf /backup/webdata.tar.gz -C /source .
Zurückspielen
  • docker run --rm -v webdata:/target -v /backup:/backup debian:13 tar xzf /backup/webdata.tar.gz -C /target
Angabe Bedeutung
--rm Der Hilfscontainer wird nach der Sicherung gelöscht
-v webdata:/source Das zu sichernde Volume wird eingehängt
-v /backup:/backup Das Zielverzeichnis des Hosts wird eingehängt
-c Archiv erzeugen
-x Archiv entpacken
-z Mit gzip komprimieren
-f Name der Archivdatei
-C Vor dem Packen bzw. Entpacken in dieses Verzeichnis wechseln

Nur-Lese-Einbindung

  • docker run -d -v /srv/web:/usr/share/nginx/html:ro nginx:1.29
Angabe Bedeutung
:ro Der Container darf nur lesen

Zertifikate, Schlüssel und Konfigurationsdateien werden grundsätzlich mit :ro eingebunden.

Übung

  • Legen Sie auf dem Host /srv/web mit einer eigenen index.html an und binden Sie das Verzeichnis in einen Nginx-Container ein.
  • Ändern Sie die Datei auf dem Host und laden Sie die Seite neu, ohne den Container anzufassen.
  • Legen Sie ein Named Volume an, hängen Sie es unter /usr/share/nginx/html ein und prüfen Sie, was Docker beim ersten Start hineinkopiert hat.
  • Binden Sie ein Named Volume in zwei Container gleichzeitig ein, schreiben Sie aus dem einen eine Datei hinein und lesen Sie sie aus dem anderen.
  • Sichern Sie das Volume nach /backup, löschen Sie es und spielen Sie es zurück.

Eigene Images bauen

Dockerfile

Ein Dockerfile ist eine Textdatei mit den Bauanweisungen. Jede Anweisung erzeugt eine Schicht.

FROM

Legt das Basis-Image fest, muss die erste Anweisung sein
  • FROM python:3.13-slim
Variante Bedeutung
python:3.13 Vollständige Debian-Basis, groß, viele Werkzeuge enthalten
python:3.13-slim Abgespeckte Debian-Basis, deutlich kleiner
python:3.13-alpine Alpine-Basis, am kleinsten, andere C-Bibliothek, gelegentlich Kompatibilitätsprobleme

WORKDIR

Setzt das Arbeitsverzeichnis für alle folgenden Anweisungen, legt es bei Bedarf an
  • WORKDIR /app

COPY

Kopiert Dateien vom Baukontext in das Image
  • COPY requirements.txt .
  • COPY app.py .
Baukontext

Der Baukontext ist das Verzeichnis, das beim Bauen als letztes Argument angegeben wird. Nur Dateien aus diesem Verzeichnis können kopiert werden.

RUN

Führt ein Kommando beim Bauen aus
  • RUN pip install --no-cache-dir -r requirements.txt
Angabe Bedeutung
--no-cache-dir pip legt keinen Paketcache im Image ab, das Image bleibt kleiner
Mehrere Kommandos zusammenfassen

Jede RUN-Anweisung erzeugt eine Schicht. Zusammengehörige Schritte gehören in eine Anweisung, das Aufräumen in dieselbe Zeile.

RUN apt-get update && apt-get install -y --no-install-recommends \
        curl \
    && rm -rf /var/lib/apt/lists/*
Angabe Bedeutung
--no-install-recommends Empfohlene, aber nicht zwingende Pakete werden weggelassen
rm -rf /var/lib/apt/lists/* Die Paketlisten werden entfernt; in einer eigenen Schicht würde das nichts bringen, weil die Daten in der darunterliegenden Schicht bleiben

ENV

Setzt eine Umgebungsvariable im Image und in jedem daraus erzeugten Container
  • ENV PYTHONUNBUFFERED=1

USER

Legt fest, unter welchem Benutzer der Hauptprozess läuft
  • RUN useradd -r -u 1001 app
  • USER app
Angabe Bedeutung
-r Systembenutzer ohne Heimatverzeichnis und ohne Anmeldung
-u 1001 Feste UID, damit die Rechte auf Volumes vorhersagbar bleiben

Ohne diese Anweisung läuft der Prozess im Container als root.

EXPOSE

Dokumentiert, auf welchem Port der Dienst lauscht
  • EXPOSE 5000

EXPOSE veröffentlicht keinen Port. Es dient als Hinweis und wird von -P ausgewertet.

CMD und ENTRYPOINT

CMD legt das Startkommando fest und ist beim Start überschreibbar
  • CMD ["python", "app.py"]
ENTRYPOINT legt das Startkommando fest, CMD liefert dann nur noch die Argumente
  • ENTRYPOINT ["python", "app.py"]
Schreibweise Bedeutung
CMD ["python", "app.py"] Exec-Form; der Prozess wird direkt gestartet und erhält die Signale von docker stop
CMD python app.py Shell-Form; es wird eine Shell dazwischengeschaltet, Signale kommen beim Prozess nicht sauber an

Es gilt immer nur die letzte CMD-Anweisung im Dockerfile. Die Exec-Form ist die einzusetzende Schreibweise.

.dockerignore

Schließt Dateien vom Baukontext aus
  • vim .dockerignore
.git
__pycache__
*.log

Ohne diese Datei wird das gesamte Verzeichnis an den Daemon übertragen, auch das Git-Verzeichnis.

Image bauen

  • docker build -t webapp:1.0 .
Angabe Bedeutung
-t webapp:1.0 Name und Tag des entstehenden Images
. Der Baukontext, hier das aktuelle Verzeichnis
Reihenfolge im Dockerfile

Selten geänderte Anweisungen gehören nach oben, häufig geänderte nach unten. Deshalb wird zuerst requirements.txt kopiert und installiert und erst danach der Anwendungscode. Eine Codeänderung macht dann nur die letzten Schichten ungültig.

Tags

  • docker tag webapp:1.0 webapp:latest
  • docker tag webapp:1.0 registry.it2XX.int/webapp:1.0
  • docker push registry.it2XX.int/webapp:1.0
Aufbau
  • docker tag Quelle Ziel

Ein Tag ist nur ein zusätzlicher Name für dasselbe Image, es wird nichts kopiert.

Mehrstufiger Bau

Bei mehrstufigen Builds wird in einer ersten Stufe gebaut und in einer zweiten nur das Ergebnis übernommen. Compiler und Entwicklungspakete landen damit nicht im fertigen Image.

FROM golang:1.24 AS build
WORKDIR /src
COPY . .
RUN go build -o /out/server .

FROM debian:13-slim
COPY --from=build /out/server /usr/local/bin/server
CMD ["server"]
Angabe Bedeutung
AS build Name der ersten Stufe
--from=build Die Datei wird aus der genannten Stufe übernommen, nicht aus dem Baukontext

Beispiel: eigene Anwendung

Projektverzeichnis anlegen
  • mkdir -p ~/webapp
  • cd ~/webapp
Anwendung
  • vim app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hallo aus dem Container\n"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
Abhängigkeiten
  • vim requirements.txt
flask==3.1.0
Bauanleitung
  • vim Dockerfile
FROM python:3.13-slim

ENV PYTHONUNBUFFERED=1
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

RUN useradd -r -u 1001 app
USER app

EXPOSE 5000
CMD ["python", "app.py"]
Bauen und starten
  • docker build -t webapp:1.0 .
  • docker run -d --name webapp -p 5000:5000 webapp:1.0
  • curl http://localhost:5000

Übung

  • Bauen Sie das Image webapp:1.0 und starten Sie es.
  • Ändern Sie den Text in app.py, bauen Sie erneut und beobachten Sie, welche Schichten aus dem Cache kommen.
  • Tauschen Sie im Dockerfile die Reihenfolge, kopieren Sie also app.py vor der Installation der Abhängigkeiten, bauen Sie erneut und vergleichen Sie die Bauzeit.
  • Prüfen Sie mit docker exec webapp id, unter welchem Benutzer der Prozess läuft.
  • Legen Sie eine .dockerignore an und vergleichen Sie die Größe des übertragenen Baukontexts in der Ausgabe von docker build.

Docker Compose

Was ist Compose

Compose beschreibt mehrere Container samt Netzwerken und Volumes in einer YAML-Datei und betreibt sie gemeinsam.

Aktueller Stand

Compose ist ein Unterkommando von Docker und wird als docker compose aufgerufen. Das alte, separat installierte Python-Werkzeug docker-compose mit Bindestrich wird nicht mehr gepflegt. In Anleitungen im Netz steht häufig noch die alte Schreibweise.

Aufbau der Datei

Dateiname

Der aktuelle Standardname ist compose.yaml. docker-compose.yml wird aus Kompatibilitätsgründen weiterhin gelesen.

Keine Versionsangabe

Die früher übliche erste Zeile version: '3.8' ist entfallen. Sie wird ignoriert und erzeugt beim Start eine Warnung.

  • vim compose.yaml
services:
  web:
    image: nginx:1.29
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    restart: unless-stopped

  app:
    build: ./app
    environment:
      APP_ENV: production
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: geheim
    volumes:
      - dbdata:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  dbdata:
Schlüssel Bedeutung
services Die einzelnen Container
image Fertiges Image aus einer Registry
build Verzeichnis mit einem Dockerfile, aus dem gebaut wird
ports Portweiterleitungen wie bei -p
volumes Einbindungen wie bei -v
environment Umgebungsvariablen
depends_on Startreihenfolge
restart Neustartverhalten
volumes (unten) Deklaration der benannten Volumes

Netzwerk

Compose legt für jedes Projekt ein eigenes Netzwerk an. Alle Dienste des Projekts hängen darin und erreichen sich über den Dienstnamen als Hostnamen. Der Dienst app erreicht die Datenbank also über den Namen db, ohne dass ein Port veröffentlicht werden muss.

Wichtig

Nur Dienste, die von außen erreichbar sein müssen, bekommen einen Eintrag unter ports. Alles andere bleibt im internen Netz.

Projekt starten und stoppen

  • docker compose up -d
  • docker compose ps
  • docker compose stop
  • docker compose down
  • docker compose down -v
Kommando Bedeutung
up -d Erzeugt und startet alle Dienste im Hintergrund
ps Zustand der Dienste des Projekts
stop Hält die Container an, löscht sie aber nicht
down Hält an und entfernt Container und Netzwerk
down -v Zusätzlich werden die Volumes des Projekts gelöscht
Projektname

Der Projektname ist standardmäßig der Name des Verzeichnisses. Alle erzeugten Container, Netzwerke und Volumes tragen ihn als Präfix.

Weitere Kommandos

  • docker compose logs -f
  • docker compose logs -f web
  • docker compose up -d --build
  • docker compose exec web bash
  • docker compose run --rm app bash
  • docker compose up -d --scale app=3
  • docker compose config
Kommando Bedeutung
logs -f Logs aller Dienste mitlaufen lassen
logs -f web Logs eines einzelnen Dienstes
up -d --build Vor dem Start werden die selbst gebauten Images neu erzeugt
exec web bash Shell in einem bereits laufenden Dienst
run --rm app bash Startet einen zusätzlichen, einmaligen Container des Dienstes; --rm löscht ihn danach
up -d --scale app=3 Startet den Dienst in drei Instanzen
config Zeigt die zusammengesetzte und geprüfte Konfiguration; erste Anlaufstelle bei YAML-Fehlern
Grenzen von scale

Ein Dienst mit einem festen Eintrag unter ports oder mit container_name lässt sich nicht vervielfältigen, weil Host-Port und Name nur einmal vergeben werden können. Sinnvoll ist --scale daher nur für Dienste hinter einem Reverse Proxy.

Healthcheck und depends_on

depends_on wartet nur darauf, dass der Container gestartet ist, nicht darauf, dass der Dienst darin bereit ist. Mit einem Healthcheck lässt sich das ändern.

services:
  app:
    image: webapp:1.0
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: geheim
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
Schlüssel Bedeutung
test Kommando, das den Zustand prüft; Rückgabewert 0 bedeutet gesund
CMD-SHELL Das Kommando wird in einer Shell ausgeführt
interval Abstand zwischen zwei Prüfungen
timeout Zeit, nach der eine Prüfung als fehlgeschlagen gilt
retries Zahl der Fehlversuche, bis der Dienst als ungesund gilt
condition: service_healthy Der abhängige Dienst startet erst, wenn der Healthcheck gesund meldet

Übung

  • Legen Sie ein Verzeichnis ~/stack mit einer compose.yaml an, die Nginx und Ihre webapp:1.0 enthält.
  • Starten Sie den Stack und prüfen Sie mit docker compose ps und docker network ls, was Compose angelegt hat.
  • Prüfen Sie aus dem Nginx-Container heraus mit docker compose exec web curl http://app:5000, ob die Namensauflösung über den Dienstnamen funktioniert.
  • Fügen Sie bewusst einen YAML-Fehler ein und lassen Sie ihn sich mit docker compose config anzeigen.

Abschlussprojekt: Uptime Kuma hinter Caddy

Ziel

Uptime Kuma wird über HTTPS unter kuma.it2XX.int erreichbar gemacht. Caddy nimmt die verschlüsselte Verbindung an und reicht sie intern unverschlüsselt an Uptime Kuma weiter. Beide Dienste liegen in getrennten Compose-Projekten und werden über ein gemeinsames Netzwerk verbunden.

Bestandteil Aufgabe
Caddy Nimmt Port 80 und 443 an, terminiert TLS, leitet weiter
Uptime Kuma Überwachungsdienst, lauscht intern auf Port 3001
Netzwerk proxy Verbindet die beiden Projekte
Zertifikat /etc/ssl/own.crt und /etc/ssl/own.key auf dem Host
Warum zwei getrennte Projekte

Der Reverse Proxy ist ein eigenständiger Dienst, der später weitere Anwendungen bedienen soll. Jede Anwendung bekommt ihr eigenes Projektverzeichnis und kann unabhängig neu gestartet werden, ohne den Proxy anzufassen.

Namensauflösung

Der Name kuma.it2XX.int muss auf die Adresse des Docker-Hosts zeigen.

  • vim /etc/hosts
10.20.XX.10   kuma.it2XX.int kuma

Gemeinsames Netzwerk anlegen

  • docker network create proxy

Das Netzwerk wird außerhalb der Projekte angelegt, damit beide Compose-Dateien es als bestehendes Netzwerk einbinden können.

Zertifikat prüfen

  • ls -l /etc/ssl/own.crt /etc/ssl/own.key
  • openssl x509 -in /etc/ssl/own.crt -noout -subject -dates
Angabe Bedeutung
-in Zu prüfende Zertifikatsdatei
-noout Das Zertifikat selbst wird nicht ausgegeben
-subject Zeigt den Namen, auf den das Zertifikat ausgestellt ist
-dates Zeigt Beginn und Ende der Gültigkeit

Der im Zertifikat eingetragene Name muss kuma.it2XX.int lauten, sonst lehnt der Browser die Verbindung ab.

Projekt Caddy

Verzeichnis anlegen
  • mkdir -p ~/proxy
  • cd ~/proxy
Konfiguration des Proxys
  • vim Caddyfile
kuma.it2XX.int {
    tls /etc/ssl/own.crt /etc/ssl/own.key
    reverse_proxy kuma:3001
}
Zeile Bedeutung
kuma.it2XX.int { } Der Block gilt für Anfragen mit diesem Hostnamen
tls /etc/ssl/own.crt /etc/ssl/own.key Es wird das angegebene Zertifikat benutzt; damit fordert Caddy kein Zertifikat über ACME an
reverse_proxy kuma:3001 Weiterleitung an den Container kuma auf Port 3001
Compose-Datei
  • vim compose.yaml
services:
  caddy:
    image: caddy:2
    container_name: caddy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - /etc/ssl/own.crt:/etc/ssl/own.crt:ro
      - /etc/ssl/own.key:/etc/ssl/own.key:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - proxy
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:

networks:
  proxy:
    external: true
Eintrag Bedeutung
container_name: caddy Fester Containername statt des Namens aus Projekt und Dienst
./Caddyfile:/etc/caddy/Caddyfile:ro Die Konfiguration wird nur lesend eingebunden
/etc/ssl/own.crt:…:ro Zertifikat und Schlüssel werden unter demselben Pfad wie auf dem Host eingebunden, nur lesend
caddy_data Dauerhafter Zustand von Caddy
caddy_config Von Caddy erzeugte Konfigurationsdateien
networks: proxy Der Dienst hängt im gemeinsamen Netz
external: true Das Netz wird nicht von Compose angelegt, es muss vorhanden sein

Projekt Uptime Kuma

Verzeichnis anlegen
  • mkdir -p ~/kuma
  • cd ~/kuma
Compose-Datei
  • vim compose.yaml
services:
  kuma:
    image: louislam/uptime-kuma:1
    container_name: kuma
    volumes:
      - kuma_data:/app/data
    networks:
      - proxy
    restart: unless-stopped

volumes:
  kuma_data:

networks:
  proxy:
    external: true
Eintrag Bedeutung
container_name: kuma Unter diesem Namen erreicht Caddy den Dienst
kein ports-Eintrag Der Dienst ist nur im internen Netz erreichbar, nicht direkt vom Host
kuma_data:/app/data Datenbank und Konfiguration von Uptime Kuma bleiben erhalten

Starten und prüfen

Proxy starten
  • cd ~/proxy
  • docker compose up -d
Anwendung starten
  • cd ~/kuma
  • docker compose up -d
Zustand prüfen
  • docker compose ps
  • docker compose logs -f caddy
Verbindung testen
Angabe Bedeutung
-I Nur die Kopfzeilen abrufen
--cacert Das eigene Zertifikat wird zur Prüfung herangezogen

Danach die Seite im Browser aufrufen und den Ersteinrichtungsdialog von Uptime Kuma durchlaufen.

Typische Fehler

Meldung Ursache
network proxy declared as external, but could not be found Das Netzwerk wurde nicht mit docker network create proxy angelegt
dial tcp: lookup kuma on …: no such host Der Kuma-Container läuft nicht oder hängt nicht im Netz proxy
bind: address already in use Auf dem Host belegt bereits ein Dienst Port 80 oder 443
Browser meldet unbekannten Aussteller Das eigene Wurzelzertifikat ist auf dem Client nicht eingetragen
Zertifikatsname passt nicht Im Zertifikat steht ein anderer Name als kuma.it2XX.int

Übung

  • Bauen Sie beide Projekte wie beschrieben auf und rufen Sie Uptime Kuma über HTTPS auf.
  • Legen Sie in Uptime Kuma eine Überwachung für https://kuma.it2XX.int selbst an.
  • Halten Sie mit docker compose stop im Kuma-Projekt den Dienst an und sehen Sie sich an, welche Meldung Caddy im Log ausgibt.
  • Löschen Sie den Kuma-Container mit docker compose down und starten Sie ihn neu. Prüfen Sie, ob Ihre Überwachung noch vorhanden ist.
  • Erweitern Sie das Caddy-Projekt um einen zweiten Namen, der auf Ihre webapp:1.0 zeigt.

Empfehlungen für den Betrieb

Images
  • Feste Versionen statt latest verwenden
  • Zusammengehörige Schritte in einer RUN-Anweisung ausführen und im selben Schritt aufräumen
  • Selten geänderte Anweisungen nach oben, häufig geänderte nach unten
  • Mehrstufigen Bau nutzen, damit keine Bauwerkzeuge im fertigen Image landen
  • .dockerignore anlegen
Container
  • Ein Dienst je Container
  • Prozesse nicht als root laufen lassen
  • Dauerhafte Daten in Named Volumes, Konfiguration und Zertifikate als Bind Mount mit :ro
  • Nur die Ports veröffentlichen, die wirklich von außen erreichbar sein müssen
  • restart: unless-stopped für Dienste, die einen Neustart überstehen sollen
Sicherheit
  • Offizielle oder selbst gebaute Images bevorzugen
  • Images regelmäßig neu bauen, damit Sicherheitsupdates der Basis einfließen
  • Zugehörigkeit zur Gruppe docker nur an Personen vergeben, die Root-Rechte auf dem Host haben dürfen
  • Passwörter nicht ins Image, sondern über Umgebungsvariablen oder Dateien einbringen

Kommandoübersicht

Images

Kommando Bedeutung
docker pull image:tag Image herunterladen
docker images Lokale Images anzeigen
docker inspect image Metadaten anzeigen
docker history image Schichten anzeigen
docker build -t name:tag . Image bauen
docker tag quelle ziel Weiteren Namen vergeben
docker push image:tag Image in eine Registry laden
docker rmi image Image löschen
docker image prune -a Unbenutzte Images löschen

Container

Kommando Bedeutung
docker run image Container erzeugen und starten
docker ps Laufende Container anzeigen
docker ps -a Alle Container anzeigen
docker stop name Container anhalten
docker start name Container starten
docker restart name Container neu starten
docker pause name Prozesse im Container anhalten
docker unpause name Angehaltene Prozesse fortsetzen
docker rm name Container löschen
docker rm -f name Laufenden Container löschen
docker logs -f name Logs mitlaufen lassen
docker exec -it name bash Shell im Container öffnen
docker stats Ressourcenverbrauch anzeigen
docker container prune Beendete Container löschen

Volumes und Netzwerke

Kommando Bedeutung
docker volume create name Volume anlegen
docker volume ls Volumes anzeigen
docker volume inspect name Ablageort anzeigen
docker volume rm name Volume löschen
docker network create name Netzwerk anlegen
docker network ls Netzwerke anzeigen
docker network inspect name Angeschlossene Container anzeigen

Compose

Kommando Bedeutung
docker compose up -d Projekt im Hintergrund starten
docker compose up -d --build Vor dem Start neu bauen
docker compose ps Dienste des Projekts anzeigen
docker compose logs -f dienst Logs eines Dienstes
docker compose exec dienst bash Shell in einem Dienst
docker compose run --rm dienst bash Einmaligen Zusatzcontainer starten
docker compose up -d --scale dienst=3 Dienst in mehreren Instanzen starten
docker compose config Konfiguration prüfen
docker compose stop Dienste anhalten
docker compose down Container und Netzwerk entfernen
docker compose down -v Zusätzlich Volumes entfernen

System

Kommando Bedeutung
docker info Systemweite Informationen
docker version Versionen von Client und Server
docker system df Belegten Plattenplatz anzeigen
docker system prune Unbenutzte Objekte löschen