View on GitHub

server-workshop

Docker

Einzelne Prozesse werden in Containern vom Betriebssystem isoliert. Docker verhindert, dass die virtualisierten Prozesse auf das Hostsystem zugreifen. Entkopplung vom Hostsystem vereinfacht Migration: Es kann der gesamte Container auf einen anderen Server verschoben werden.

Grundbegriffe

Image

Vorbereitetes Abbild, aus dem ein Container erstellt werden kann, quasi ein “Installationspaket”. Images werden in der Regel aus einer Registry heruntergeladen. Wenn man einen Container erzeugt, wird immer angegeben, aus welchem Image er erzeugt werden soll.

Das Image beinhaltet immer das Grundsystem und meist auch die Software, die man virtualisieren möchte. Beispiele für Images:

Container

Laufendes Objekt, in dem ein Prozess aktiv ist, quasi “installierte Software”. Ein Container virtualisiert einen einzelnen Prozess, z. B. einen Webserver, eine Datenbank, einen DNS-Server, … Ein Container wird immer auf Basis eines Images erstellt.

Container sind “ephemeral”, also flüchtig: Ein Container muss jederzeit gelöscht werden können, ohne dass Daten verloren gehen. Man kann den Container also immer wieder aus dem Image erzeugen. Die Nutzdaten müssen in Volumes persistiert werden.

Volume

Speicher für Daten des Containers. Volumes werden genutzt, um Daten zu speichern, die den Container überleben (denn Container sind flüchtig). Hier muss also alles abgelegt werden, was eigene Konfiguration oder eigene Daten sind, die nicht standardmäßig zum Image oder Container gehören:

Volumes werden in das Dateisystem des Container eingehängt. Der Container sieht also gar nicht, ob eine Datei/Ordner flüchtig lokal im Container liegt oder nach außen über Volumes persistiert wird.

Volumes können als echtes Docker Volume oder als Bind Mount angelegt werden:

Zusammenfassung

Ein Container beinhaltet einen virtualisierten Prozess. Er wird auf Basis eines Images erzeugt. Alle Daten des Containers, die individuell gespeichert werden sollen, müssen in Volumes abgelegt werden.

Root-Privilegien

Docker wird standardmäßig mit Root-Rechten auf dem System ausgeführt. Der Daemon hat also vollen Zugriff auf das Betriebssystem des Servers. Das ist ein potenzielles Angriffsziel, weil ein schadhafter Container Sicherheitslücken im Daemon ausnutzen könnte, um Zugriff auf das Hostsystem zu erlangen.

Es ist grundsätzlich möglich, Docker ohne Root-Rechte auszuführen: https://docs.docker.com/engine/security/rootless/

Persönliche Einschätzung: Für produktive Umgebungen, die öffentlich übers Internet erreicht und angegriffen werden können, sollte man Docker rootless laufen lassen. Für einen lokalen Server, der im privaten Netz läuft und nicht direkt übers Inetrnet erreichbar ist, sind Root-Rechte in Ordnung. Das vereinfacht die Verwaltung enorm!

Docker Compose

Mit dem Befehl docker können alle Aspekte des Systems verwaltet werden, zum Beispiel die Erstellung und der Start von Containern. Alle benötigten Parameter müssen als Kommandozeilenargumente übergeben werden, z. B. p für weitergeleitete Netzwerkports oder v für Volumes. Das verwendete Image ist hier nextcloud:

docker run -d -p 8080:80 -v nextcloud:/var/www/html nextcloud

Großer praktischer Nachteil: Man muss diesen Befehl dokumentieren, um den Container später wieder genauso erzeugen zu können, zum Beispiel bei einem Update. Bei komplexeren Szenarien mit mehreren Containern, die miteinander interagieren (z. B. eine Nextcloud mit MySQL-Datenbank) müssen diese Einzelteile manuell zusammengebaut werden.

Docker Compose ist ein Werkzeug zur Orchestrierung von Containern. In einer Konfigurationsdatei wird angegeben, welche Container aus welchen Images und mit welchen Parametern (Ports, Volumes, etc.) erzeugt werden sollen. Mit lokalen Netzwerken kann die Kommunikation der Container untereinander gesteuert werden.

Der Containerverbund wird immer als Ganzes gestartet oder aktualisiert. Wenn man mehrere Projekte virtualisiert oder mehrere Container im Verbund betreibt, ist Docker Compose sehr zu empfehlen.

Dateistruktur

Der Verbund wird durch eine Datei docker-compose.yml konfiguriert. Idealerweise legt man für jedes Projekt eine separate Compose-Datei in einem eigenen Ordner an. Die Ordnerstruktur könnte dann so aussehen: für jedes Projekt ein Ordner mit docker-compose.yml. Daneben liegen die Ordner für die Volumes, die als Bind Mounts in den Container eingebunden sind:

/root/docker/
├─ homeassistant/
│  ├─ docker-compose.yml
│  ├─ config/
├─ pihole/
│  ├─ docker-compose.yml
│  ├─ etc-dnsmasq.d/
│  ├─ etc-pihole/
├─ mariadb/
│  ├─ docker-compose.yml

docker-compose.yml

In der Konfigurationsdatei wird unter anderem angegeben:

Einfaches Beispiel: pi-hole

services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8080:80/tcp"
    environment:
      TZ: 'Europe/Berlin'
      FTLCONF_LOCAL_IPV4: '192.168.1.10'
    volumes:
      - './etc-pihole:/etc/pihole'
      - './etc-dnsmasq.d:/etc/dnsmasq.d'
    restart: unless-stopped

Komplexeres Beispiel: Home Assistant

services:
  homeassistant:
    container_name: homeassistant
    image: "ghcr.io/home-assistant/home-assistant:stable"
    volumes:
      - ./config:/config
      - /etc/localtime:/etc/localtime:ro
    restart: unless-stopped
    privileged: true
    ports:
      - 8123:8123
    depends_on:
      - mariadb
    devices:
      - /dev/ttyACM0

  mariadb:
    image: mariadb
    container_name: mariadb
    restart: unless-stopped
    volumes:
      - mariadbx:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
      MYSQL_DATABASE: "${MYSQL_DATABASE}"
      MYSQL_USER: "${MYSQL_USER}"
      MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
      PUID: 1000
      PGID: 1000

#  phpmyadmin:
#    image: phpmyadmin
#    restart: unless-stopped
#    environment:
#      PMA_HOST: mariadb
#    ports:
#      - 8080:80

volumes:
  mariadbx:

Verbund starten und stoppen

# starten
docker compose up

# starten und detachen (läuft im Hintergrund)
docker compose up -d

# beenden
docker compose down

# beendete Container löschen
docker compose rm

# aktuelle Versionen der Images herunterladen
docker compose pull

Regelmäßige Updates

Um Updates zu installieren, kann man regelmäßig ausführen:

docker compose pull

# falls etwas gepullt wurde, Container neustarten
docker compose down && docker compose up -d

Tipp: Converter von docker run zu docker compose

https://it-tools.tech/docker-run-to-docker-compose-converter