Docker Caddy

Aus Xinux Wiki
Version vom 20. Oktober 2026, 19:38 Uhr von Thomas.will (Diskussion | Beiträge) (Neuer Artikel: Caddy, Uptime Kuma und HedgeDoc mit Docker auf Debian (Nachfolger von Rocky container))
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springen Zur Suche springen

Installation

DATEN

Parameter Wert Erläuterung
Server caddy Debian-Server, Grundkonfiguration per Skript
Betriebssystem Debian 13
FQDN caddy.it2XX.xinmen.de Fully Qualified Domain Name
DOM it2XX.xinmen.de Domain Name
Dienst 1 kuma.it2XX.xinmen.de Uptime Kuma (Monitoring)
Dienst 2 hedgedoc.it2XX.xinmen.de HedgeDoc (kollaborative Notizen)
IP, GW, NS per Skript werden von der Grundkonfiguration gesetzt

Grundkonfiguration

Der Server wird über ein Skript vorkonfiguriert (Netzwerk, Hostname, Benutzer, Zertifikate).

Voraussetzung im DNS

Die Namen der Dienste müssen auf den Server zeigen, z. B. als CNAME auf caddy.it2XX.xinmen.de:

kuma.it2XX.xinmen.de      CNAME  caddy.it2XX.xinmen.de.
hedgedoc.it2XX.xinmen.de  CNAME  caddy.it2XX.xinmen.de.

Container mit Docker: Caddy, Uptime Kuma und HedgeDoc

Auf dem Server caddy.it2XX.xinmen.de laufen Dienste als Docker-Container. Als praktisches Beispiel dienen drei Dienste: Caddy (Reverse Proxy), Uptime Kuma (Monitoring) und HedgeDoc (kollaborative Notizen). Jeder Dienst bekommt sein eigenes Verzeichnis mit eigener compose.yaml. Das entspricht der Praxis, Container-Stacks unabhängig voneinander verwaltbar zu halten (eigenes Update, eigenes Down/Up, ohne andere Dienste zu berühren). Verwaltet werden die Stacks mit docker compose.

Im weiteren Verlauf des Kurses werden diese Container überwacht, z. B. mit Wazuh. Der Aufbau ist deshalb von Anfang an so angelegt, dass Logs an festen Stellen landen (siehe Vorbereitung für die Überwachung).

Was ist Docker

Docker ist eine Container-Engine. Ein Container ist ein normaler Linux-Prozess, der durch Kernel-Funktionen (Namespaces, cgroups) vom Rest des Systems abgeschottet ist und sein eigenes Dateisystem aus einem Image mitbringt.

Aufbau
  • dockerd: Der Docker-Daemon läuft dauerhaft als root und verwaltet alle Container, Images, Netze und Volumes.
  • containerd: startet und überwacht die eigentlichen Container-Prozesse.
  • docker (CLI): spricht über den Socket /run/docker.sock mit dem Daemon.
Sicherheitsrelevant
  • Wer auf /run/docker.sock zugreifen darf, hat praktisch root-Rechte auf dem Host. Mitglieder der Gruppe docker sind damit root-gleich.
  • Der Daemon läuft als root. Ein Ausbruch aus einem Container kann deshalb beim Host-root landen, wenn der Container falsch konfiguriert ist (z. B. --privileged, gemounteter Docker-Socket).
Unterschied zu Podman

Podman kommt ohne zentralen Daemon aus und läuft standardmäßig rootless. Die Befehle sind nahezu identisch, docker und podman lassen sich meist 1:1 austauschen.

Merksatz

Docker = ein Daemon (root) verwaltet alle Container | Zugriff auf den Docker-Socket = root

Docker installieren

Installiert wird aus dem offiziellen Docker-Repository.

Schlüssel und Repository einrichten
Docker und das Compose-Plugin installieren
  • apt update
  • apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Prüfen
  • systemctl status docker
  • docker version
  • docker compose version
Hinweis

Compose ist heute ein Plugin von Docker. Der Befehl heißt docker compose (mit Leerzeichen), nicht mehr docker-compose.

Docker-Logging festlegen

Docker schreibt die Ausgabe jedes Containers in eine Datei unter /var/lib/docker/containers/. Ohne Begrenzung wachsen diese Dateien unbegrenzt. Die Rotation wird einmal zentral festgelegt.

Daemon-Konfiguration anlegen
  • vim /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
Docker neu starten
  • systemctl restart docker
Hinweis

Die Einstellung gilt für Container, die danach erstellt werden. Der Treiber json-file ist wichtig für die spätere Überwachung, weil ein Agent wie Wazuh diese Dateien direkt lesen kann.

Vorbereitung: Zertifikate

Die Zertifikate liegen bereits vor unter:

  • /etc/ssl/own.crt
  • /etc/ssl/own.key

Das Zertifikat muss die Namen kuma.it2XX.xinmen.de und hedgedoc.it2XX.xinmen.de enthalten (oder ein Wildcard-Zertifikat für *.it2XX.xinmen.de sein).

Verzeichnisstruktur

Jeder Dienst bekommt ein eigenes Verzeichnis mit eigener Compose-Datei. Der Dateiname ist immer compose.yaml, das ist der aktuelle Standardname (der alte Name docker-compose.yml funktioniert noch, wird aber nicht mehr verwendet). docker compose sucht beim Aufruf automatisch nach dieser Datei im aktuellen Verzeichnis.

/opt/docker/
├── caddy/
│   ├── compose.yaml
│   └── Caddyfile
├── uptime-kuma/
│   └── compose.yaml
└── hedgedoc/
    ├── compose.yaml
    └── .env
Struktur anlegen
  • mkdir -p /opt/docker/{caddy,uptime-kuma,hedgedoc}
  • mkdir -p /var/log/caddy
Merksatz

Ein Verzeichnis = ein Dienst = ein eigenständiges Compose-Projekt.

Wie Container sich im Netzwerk finden

Docker legt für jedes selbst angelegte Netzwerk ein internes DNS an. Jeder Container ist darin unter seinem container_name erreichbar, nicht unter localhost und nicht über die Host-IP.

Merksatz

container_name = Hostname im internen Netz. Zwei Container im selben Netzwerk erreichen sich per ping container_name bzw. curl http://container_name:PORT.

Wichtige Besonderheit bei getrennten Verzeichnissen: Jedes Compose-Projekt legt sein eigenes, isoliertes Netzwerk an. Liegen Caddy und Uptime Kuma in unterschiedlichen Verzeichnissen, finden sie sich nicht automatisch. Die Lösung: Ein gemeinsames Netzwerk wird einmalig manuell und projektübergreifend angelegt, und jede compose.yaml bindet sich als external ein.

Einmalig das gemeinsame Netzwerk anlegen
  • docker network create proxy
Prüfen
  • docker network ls
            Internet / Schulungsnetz
                     │ 80/443
              ┌──────┴──────┐
              │    caddy    │   einziger Container mit Ports am Host
              └──────┬──────┘
     Netz "proxy"    │
        ┌────────────┴────────────┐
 ┌──────┴──────┐           ┌──────┴──────┐
 │ uptime-kuma │           │  hedgedoc   │
 │    :3001    │           │    :3000    │
 └─────────────┘           └──────┬──────┘
                    internes Netz │ (nur HedgeDoc-Projekt)
                           ┌──────┴──────┐
                           │ hedgedoc-db │
                           │    :5432    │
                           └─────────────┘

Dienst 1: Caddy (Reverse Proxy)

Caddy ist der einzige Container, der Ports am Host öffnet, weil er der einzige Kontaktpunkt von außen ist. Er terminiert TLS mit dem vorhandenen Zertifikat und leitet intern per HTTP an die Dienste weiter.

Compose-Datei anlegen
  • vim /opt/docker/caddy/compose.yaml
services:
  caddy:
    image: caddy:2
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - /etc/ssl/own.crt:/etc/ssl/own.crt:ro
      - /etc/ssl/own.key:/etc/ssl/own.key:ro
      - /var/log/caddy:/var/log/caddy
      - caddy-data:/data
      - caddy-config:/config
    networks:
      - proxy

volumes:
  caddy-data:
  caddy-config:

networks:
  proxy:
    external: true
Erklärung
  • restart: unless-stopped: Docker startet den Container nach einem Absturz oder Neustart des Servers wieder, außer man hat ihn selbst gestoppt. Ohne diese Zeile bleibt der Dienst nach einem Reboot aus.
  • :ro: Caddyfile und Zertifikate werden nur lesend eingebunden. Der Container kann sie nicht verändern.
  • 443:443/udp: für HTTP/3. Kann entfallen, schadet aber nicht.
  • /var/log/caddy: Hier schreibt Caddy seine Zugriffslogs, direkt auf dem Host lesbar.
Hinweis depends_on

Caddy und die Dienste liegen in getrennten Compose-Projekten, ein depends_on zwischen ihnen gibt es deshalb nicht (das funktioniert nur innerhalb eines Projekts). Das ist unproblematisch: Caddy versucht bei jeder Anfrage erneut, den Dienst zu erreichen.

Caddyfile

Die Caddyfile ist Caddys Konfigurationsdatei (reiner Text, kein YAML/JSON). Sie legt pro Domain fest, welches Zertifikat benutzt wird und wohin intern weitergeleitet wird.

Caddyfile anlegen
  • vim /opt/docker/caddy/Caddyfile
(logging) {
    log {
        output file /var/log/caddy/access.log
        format json
    }
}

kuma.it2XX.xinmen.de {
    tls /etc/ssl/own.crt /etc/ssl/own.key
    import logging
    reverse_proxy uptime-kuma:3001
}

hedgedoc.it2XX.xinmen.de {
    tls /etc/ssl/own.crt /etc/ssl/own.key
    import logging
    reverse_proxy hedgedoc:3000
}

Aufbau:

  • (logging) { … }: ein wiederverwendbarer Baustein (Snippet). Er wird in jedem Host-Block mit import logging eingebunden.
  • log … format json: Jeder Zugriff wird als eine JSON-Zeile in /var/log/caddy/access.log geschrieben. JSON lässt sich später von Wazuh ohne eigenen Decoder auswerten.
  • kuma.it2XX.xinmen.de {: für welchen Hostnamen der Block gilt.
  • tls …: welches Zertifikat und welcher Schlüssel verwendet werden.
  • reverse_proxy uptime-kuma:3001: wohin die entschlüsselte Anfrage intern geht (container_name als DNS-Name).
Caddyfile prüfen, bevor Caddy sie lädt
  • docker run --rm -v /opt/docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2 caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile

Die Prüfung meldet einen Fehler, solange die Zertifikate nicht eingebunden sind. Alternativ nach dem Start:

  • docker exec caddy caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
Caddyfile im laufenden Betrieb neu laden
  • docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile

Dienst 2: Uptime Kuma

Uptime Kuma braucht keinen Port am Host. Es lauscht intern auf Port 3001 und wird ausschließlich über Caddy nach außen gereicht. Die Daten (SQLite-Datenbank, Konfiguration) liegen in einem benannten Volume, damit sie Neustarts und Updates überleben.

Compose-Datei anlegen
  • vim /opt/docker/uptime-kuma/compose.yaml
services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: unless-stopped
    volumes:
      - uptime-kuma-data:/app/data
    networks:
      - proxy

volumes:
  uptime-kuma-data:

networks:
  proxy:
    external: true
Wichtig

Kein ports:-Eintrag. Ein Dienst, der nur von Caddy angesprochen wird, muss nicht am Host offen sein. Ein offener Port wäre sogar ein unnötiges Risiko: Er läge ungeschützt ohne TLS am Netz.

Achtung Docker und Firewall

Ports, die mit ports: veröffentlicht werden, trägt Docker selbst in die Paketfilter-Regeln ein. Sie sind dann von außen erreichbar, auch wenn eine Host-Firewall (nftables, ufw) den Port eigentlich sperrt. Deshalb nur dort Ports veröffentlichen, wo es wirklich nötig ist: hier nur bei Caddy.

Dienst 3: HedgeDoc mit Datenbank

HedgeDoc braucht eine Datenbank. Sie läuft als zweiter Container im selben Compose-Projekt. Die Datenbank hängt nur im internen Netz des Projekts, HedgeDoc zusätzlich im Netz proxy. Caddy erreicht so HedgeDoc, aber niemals die Datenbank.

Passwort in eine eigene Datei
  • vim /opt/docker/hedgedoc/.env
DB_PASSWORD=EinLangesPasswortHierEintragen
  • chmod 600 /opt/docker/hedgedoc/.env
Compose-Datei anlegen
  • vim /opt/docker/hedgedoc/compose.yaml
services:
  database:
    image: postgres:16-alpine
    container_name: hedgedoc-db
    restart: unless-stopped
    environment:
      - POSTGRES_USER=hedgedoc
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=hedgedoc
    volumes:
      - hedgedoc-db:/var/lib/postgresql/data
    networks:
      - intern

  hedgedoc:
    image: quay.io/hedgedoc/hedgedoc:1.10.0
    container_name: hedgedoc
    restart: unless-stopped
    environment:
      - CMD_DB_URL=postgres://hedgedoc:${DB_PASSWORD}@hedgedoc-db:5432/hedgedoc
      - CMD_DOMAIN=hedgedoc.it2XX.xinmen.de
      - CMD_PROTOCOL_USESSL=true
      - CMD_URL_ADDPORT=false
    volumes:
      - hedgedoc-uploads:/hedgedoc/public/uploads
    depends_on:
      - database
    networks:
      - intern
      - proxy

volumes:
  hedgedoc-db:
  hedgedoc-uploads:

networks:
  intern:
  proxy:
    external: true
Erklärung
  • .env: docker compose liest diese Datei im Projektverzeichnis automatisch. ${DB_PASSWORD} wird beim Start ersetzt. Das Passwort steht so nicht in der compose.yaml.
  • networks: intern: wird vom Projekt selbst angelegt (ohne external). Nur die beiden HedgeDoc-Container hängen darin.
  • depends_on: Innerhalb desselben Projekts funktioniert es. Die Datenbank wird vor HedgeDoc gestartet.
Hinweis Reverse-Proxy-Variablen

HedgeDoc muss wissen, unter welcher URL es von außen erreichbar ist. Sonst erzeugt es Links auf CSS und JavaScript mit falscher Adresse und die Seite erscheint ohne Styling:

  • CMD_DOMAIN: externer Hostname
  • CMD_PROTOCOL_USESSL=true: Links als https:// erzeugen, obwohl HedgeDoc intern nur HTTP spricht (TLS macht Caddy)
  • CMD_URL_ADDPORT=false: keinen Port an die URLs hängen
Merksatz

Ein neuer Dienst braucht vier Dinge: eigenes Verzeichnis, eigener container_name, Anbindung an das Netz proxy mit external: true, eigener Host-Block in der Caddyfile mit container_name:Port. Nach außen offen sind immer nur 80/443 bei Caddy.

Starten und Stoppen

Jeder Dienst wird aus seinem eigenen Verzeichnis gestartet. Die Reihenfolge ist egal, solange das Netzwerk proxy existiert.

Dienste starten
  • cd /opt/docker/uptime-kuma && docker compose up -d
  • cd /opt/docker/hedgedoc && docker compose up -d
  • cd /opt/docker/caddy && docker compose up -d
Status prüfen
  • docker ps
Einzelnen Dienst stoppen
  • cd /opt/docker/caddy && docker compose down

Test

Aufruf im Browser
https://kuma.it2XX.xinmen.de
https://hedgedoc.it2XX.xinmen.de

Beim ersten Aufruf fragt Uptime Kuma nach einem Admin-Account. Danach lassen sich Monitore anlegen (Ping, TCP-Port, HTTP), z. B. für https://hedgedoc.it2XX.xinmen.de.

Zugriffslog ansehen
  • tail -f /var/log/caddy/access.log
Nur die wichtigsten Felder (mit jq)
  • apt install -y jq
  • tail -f /var/log/caddy/access.log | jq -c '{ts, ip: .request.remote_ip, host: .request.host, uri: .request.uri, status}'

Vorbereitung für die Überwachung

Im weiteren Kurs werden die Container überwacht, z. B. mit einem Wazuh-Agenten auf diesem Server. Diese Quellen sind dafür interessant:

Quelle Wo Was steht drin
Caddy-Zugriffslog /var/log/caddy/access.log jeder HTTP-Zugriff als JSON: Quell-IP, Host, URL, Statuscode, User-Agent
Container-Ausgaben /var/lib/docker/containers/*/*-json.log stdout und stderr jedes Containers, z. B. Fehlermeldungen von HedgeDoc oder der Datenbank
Docker-Ereignisse docker events Container gestartet, gestoppt, gelöscht, Image gezogen, docker exec ausgeführt
Docker-Daemon journalctl -u docker Meldungen des Daemons selbst
Dateien und Socket /etc/docker, /run/docker.sock, /opt/docker Änderungen an Konfiguration und compose-Dateien, Zugriffe auf den Socket (auditd)
Docker-Ereignisse live ansehen
  • docker events

In einem zweiten Terminal z. B. docker exec -it caddy sh ausführen: Im ersten Terminal erscheint ein exec_create- und exec_start-Ereignis. Genau solche Ereignisse sind für die Überwachung interessant, weil ein Angreifer oder ein neugieriger Admin so in einen Container gelangt.

Merksatz

Was überwacht werden soll, muss zuerst an einer festen Stelle protokolliert werden: Caddy schreibt nach /var/log/caddy, Docker nach /var/lib/docker/containers, Ereignisse liefert docker events.

Cheat Sheet: Docker und docker compose

Container

Laufende Container anzeigen
  • docker ps
Alle Container anzeigen (auch gestoppte)
  • docker ps -a
Logs eines Containers anzeigen
  • docker logs caddy
Logs live mitverfolgen (wie tail -f)
  • docker logs -f uptime-kuma
Nur die letzten 50 Zeilen
  • docker logs --tail 50 hedgedoc
Shell im laufenden Container öffnen
  • docker exec -it caddy sh
Einzelnen Befehl im Container ausführen
  • docker exec caddy cat /etc/caddy/Caddyfile
Container neu starten
  • docker restart caddy
Container stoppen / starten
  • docker stop caddy
  • docker start caddy
Details zu einem Container (IP, Mounts, Umgebungsvariablen)
  • docker inspect uptime-kuma
Wo liegt die Logdatei eines Containers
Ressourcenverbrauch live anzeigen
  • docker stats

Images

Lokale Images anzeigen
  • docker images
Image aktualisieren (neue Version ziehen)
  • docker pull louislam/uptime-kuma:1
Ungenutzte Images aufräumen
  • docker image prune

Netzwerke und Volumes

Netzwerke anzeigen
  • docker network ls
Details zu einem Netzwerk (welche Container hängen drin, IPs)
  • docker network inspect proxy
Volumes anzeigen
  • docker volume ls
Wo liegt ein Volume auf dem Host
  • docker volume inspect uptime-kuma_uptime-kuma-data
Hinweis

docker compose stellt Volume-Namen den Projektnamen voran, also dem Verzeichnisnamen: aus uptime-kuma-data im Verzeichnis uptime-kuma wird uptime-kuma_uptime-kuma-data.

docker compose (im jeweiligen Dienst-Verzeichnis)

Stack starten (im Hintergrund)
  • docker compose up -d
Stack stoppen und Container entfernen (Volumes bleiben erhalten)
  • docker compose down
Status der Container dieses Projekts
  • docker compose ps
Logs aller Container dieses Projekts
  • docker compose logs
Logs live mitverfolgen
  • docker compose logs -f
Dienst aktualisieren (neues Image ziehen, dann neu erstellen)
  • docker compose pull
  • docker compose up -d
Fertige Konfiguration anzeigen (mit eingesetzten Variablen aus .env)
  • docker compose config

Troubleshooting-Reihenfolge

Wenn ein Dienst nicht erreichbar ist, immer in dieser Reihenfolge prüfen:

  1. Läuft der Container überhaupt? → docker ps
  2. Was sagen die Logs? → docker logs -f containername
  3. Hängt er im richtigen Netz? → docker network inspect proxy
  4. Ist der Dienst intern erreichbar? → docker exec -it caddy sh, dann wget -O- http://uptime-kuma:3001
  5. Stimmt die Caddyfile? → docker exec caddy caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
  6. Kommt die Anfrage bei Caddy an? → tail -f /var/log/caddy/access.log
  7. Zeigt der Name auf den Server? → host kuma.it2XX.xinmen.de
Merksatz

docker logs -f = erste Anlaufstelle bei jedem Problem | docker exec -it NAME sh = das Problem von innen ansehen | docker network inspect = wer hängt wirklich in welchem Netz

Merksatz (Zusammenfassung)

docker compose = ein YAML-File statt mehrerer docker-run-Befehle | getrenntes Verzeichnis pro Dienst = unabhängige Compose-Projekte | gemeinsames Netz einmalig anlegen und in jeder compose.yaml als external einbinden | container_name = DNS-Name im internen Netz | nur Caddy veröffentlicht Ports | restart: unless-stopped, sonst bleibt der Dienst nach dem Reboot aus | Logs an feste Stellen schreiben, damit sie später überwacht werden können