Files
lpic-sim/ai/common/docker.md
T
Björn RundenandClaude Opus 5.5 7f6b8ebe07 Basis aus 2nd-brain für LPIC-Prüfungsvorbereitung übernommen
Regeln, Standards, Kontextstruktur und Werkzeuge übernommen; 2nd-brain-
Inhalte (Stacks, Ansible-Inventar, Architektur, Knowledge) entfernt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:56:53 +01:00

11 KiB
Raw Blame History

Docker-Compose-Standard (verbindlich)

Gilt für jeden Docker-Stack, der hier entsteht. Nicht zur Diskussion stellen – vollständig „durchdeklinieren“. Abweichungen nur, wenn das Image es technisch erzwingt; dann im Stack-README begründen. Vorlagen: templates/docker/. Umgebungswerte (<…>) stehen in ai/context/environment.md.

1. Pflichtbestandteile je Service

# Vorgabe Warum
1 restart: unless-stopped stabiler Betrieb, manuelles Stoppen bleibt respektiert
2 logging: *default-logging (GELF → Graylog) zentrales Logging
3 container_name: ${COMPOSE_PROJECT_NAME} (mehrere Services: ${COMPOSE_PROJECT_NAME}_<service>) eindeutige Namen, GELF-Tag passt
4 env_file: .env statt aufgeblähtem environment: Stackfile bleibt transportabel
5 Persistenz nur per Bind-Mount aus .env (${DIR_*}), keine Named Volumes Stack-Verzeichnis = komplettes Backup, umziehbar
6 healthcheck „guter Ton“; Test passend zum Image (s. 4.)
7 Traefik-Labels (s. 3.) für jeden Service mit Web-Oberfläche/API Exposition über Reverse Proxy
8 Image-Tag aus .env (${<APP>_TAG}), nie latest reproduzierbar, Updates bewusst
9 Alle host-/umgebungsspezifischen Werte in .env (Hosts, IPs, Ports, Pfade, Domains, extra_hosts) Stackfile enthält keine Umgebungsdaten
10 Namen ohne Bindestrich – Unterstrich statt -: COMPOSE_PROJECT_NAME, Stack-Verzeichnis, Service-, Container- und Router-Namen (z. B. hashi_vault, nicht hashi-vault) GELF-Tag = Projektname; _ läuft in der Elasticsearch-Suche sauber (- wird als Worttrenner zerlegt)
11 Header mit Verantwortlichen im docker-compose.yml (rules/headers.md) Zuständigkeit sofort sichtbar

2. Verzeichnislayout je Stack

<STACK_BASE>/<stack>/            # <stack> = COMPOSE_PROJECT_NAME
├── docker-compose.yml
├── .env                       # echte Werte inkl. Secrets – nie in Git/extern
├── .env.example               # gleiche Schlüssel, Platzhalter statt Secrets
├── README.md                  # Stack-Doku (s. 5.)
└── data/ config/ …            # Bind-Mount-Ziele (über DIR_* referenziert)

3. Vorlagen

3.1 docker-compose.yml

x-logging: &default-logging
  driver: gelf
  options:
    gelf-address: "udp://${GELF_HOST}:${GELF_PORT}"
    tag: "${COMPOSE_PROJECT_NAME}"
    labels: "com.docker.compose.project,com.docker.compose.service"

services:
  app:
    image: <hersteller>/<image>:${APP_TAG}
    container_name: ${COMPOSE_PROJECT_NAME}
    restart: unless-stopped
    env_file: .env
    # ports: nur wenn Zugriff ohne Traefik nötig ist
    #  - "${APP_PORT}:<intern>"
    extra_hosts:                      # nur wenn benötigt; ein Eintrag je Variable
      - "${EXTRA_HOST_1}"
    volumes:
      - ${DIR_DATA}:/<pfad-im-container>
      - ${DIR_CONFIG}:/<config-pfad>:ro
    networks:
      - proxy
    labels:                           # Traefik bleibt aus, bis TRAEFIK_ENABLE=true (s. 3.2)
      - "traefik.enable=${TRAEFIK_ENABLE:-false}"
      - "traefik.docker.network=${TRAEFIK_NETWORK:-traefik-net}"
      - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.rule=Host(`${APP_FQDN:-${COMPOSE_PROJECT_NAME}.invalid}`)"
      - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}"
      - "traefik.http.services.${COMPOSE_PROJECT_NAME}.loadbalancer.server.port=${APP_INTERNAL_PORT:-80}"
      # bei Bedarf manuell ergänzen: entrypoints, middlewares …
    logging: *default-logging
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:<intern>/<pfad>"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s

networks:
  proxy:
    name: ${TRAEFIK_NETWORK:-traefik-net}
    external: true

3.2 Traefik-Labels: Dummy zuerst, Freischaltung per Schalter

  • Labels stehen immer direkt im Stackfile und werden je Stack manuell angepasst (kein extends, keine Auslagerung).
  • traefik.enable=${TRAEFIK_ENABLE:-false} → solange in .env nicht TRAEFIK_ENABLE=true steht, ignoriert Traefik den Container: kein Router, keine Let's-Encrypt-Anfrage.
  • Ablauf: Stack ohne Traefik hochfahren → Healthcheck healthy, Funktion geprüft, FQDN/DNS stimmt → erst dann TRAEFIK_ENABLE=true + docker compose up -d.
  • Dummy-FQDN <stack>.invalid (reservierte TLD) verhindert, dass ein vergessener Wert je eine echte Domain anfragt.
  • Mehrere Web-Services: Router-/Service-Namen eindeutig halten (${COMPOSE_PROJECT_NAME}_<service>).

3.3 .env / .env.example

# --- Stack ---
COMPOSE_PROJECT_NAME=<stack>        # nur a-z, 0-9, _ – kein Bindestrich (GELF-Tag)
APP_TAG=<version>

# --- Logging (Graylog) ---
GELF_HOST=<GELF_HOST>
GELF_PORT=12201

# --- Traefik (Defaults: traefik-net / le – nur bei Abweichung setzen) ---
TRAEFIK_ENABLE=false            # erst auf true, wenn Stack sauber läuft (LE-Limits!)
APP_FQDN=<app.domain.tld>
APP_INTERNAL_PORT=<port>
#TRAEFIK_NETWORK=traefik-net
#TRAEFIK_CERTRESOLVER=le

# --- Hosts ---
EXTRA_HOST_1=<fqdn>:<ip>

# --- Pfade (Bind-Mounts, relativ zum Stack) ---
DIR_DATA=./data
DIR_CONFIG=./config

# --- Image-spezifische Variablen (laut Orig-Doku, s. README) ---

3.4 Geprüfte, aber verworfene Wege (Compose v5.0.2, 06.10.2026)

  • label_file: – keine Variablen im Schlüssel möglich (Router-Name!) und Werte werden nicht interpoliert. Unbrauchbar für Traefik.
  • extends aus gemeinsamer traefik.yml – technisch ok, vom Bediener abgelehnt: Labels gehören sichtbar und manuell anpassbar ins Stackfile.
  • Separate .traefik.env per COMPOSE_ENV_FILES in .env – wirkt nicht; nur als Shell-Variable oder per --env-file bei jedem Aufruf → fehleranfällig.

4. Hinweise zur Umsetzung

  • extra_hosts in .env: ja, möglich – Compose interpoliert Listenwerte. Ein Eintrag je Variable (EXTRA_HOST_1=host01.example.org:192.0.2.10). Schlüssel in Map-Syntax werden nicht interpoliert → Listen-Syntax verwenden. Leere Variable ergibt einen ungültigen Eintrag → Zeile nur aufnehmen, wenn genutzt.
  • Healthcheck: vorher prüfen, ob wget/curl im Image existieren (docker run --rm --entrypoint sh <image> -c 'command -v wget curl'). Endpunkt wählen, der im Normalbetrieb nicht fälschlich failt (z. B. Vault: /ui/ statt /v1/sys/health, weil versiegelt = 503).
  • env_file: .env reicht auch Stack-Variablen (GELF_, DIR_, TRAEFIK_*) in den Container durch – ist so gewollt und in der Regel harmlos.
  • Kommentare im Stackfile nur für Nicht-Offensichtliches (wie im Vault-Healthcheck).

5. Stack-README (Pflicht)

Jeder Stack bekommt ein README.md mit:

  1. Zweck und Zugriff (FQDN, ggf. Port).
  2. Orig-Doku: Links zu Image-Seite (Docker Hub / GHCR) und Hersteller-Doku, Abrufdatum.
  3. ENV-Variablen laut Orig-Doku: Tabelle Variable | Bedeutung | Default | gesetzt in .env? – vollständig aus der Herstellerdoku übernommen, nicht geraten.
  4. Volumes/Pfade laut Orig-Doku (welcher Container-Pfad persistiert was).
  5. Ports intern/extern, Healthcheck-Endpunkt und Begründung.
  6. Besonderheiten (Capabilities wie IPC_LOCK, Init-/Unseal-Schritte, Update-Hinweise, Abweichungen vom Standard).

Vor dem Schreiben eines Stacks die Orig-Doku per Web-Recherche holen (erlaubt laut rules/web_search.md).

6. Abnahme-Checkliste

  • Punkte 1–11 aus Abschnitt 1 erfüllt
  • Kein - und kein __ in Projekt-, Service-, Container- und Router-Namen
  • sultan-Host: Port veröffentlicht, kein externes Traefik-Netz, entrypoints-Label (7.1)
  • Keine Hosts/IPs/Domains/Pfade/Secrets im Stackfile
  • .env.example vollständig, ohne Secrets
  • README mit Orig-Doku und ENV-Tabelle
  • docker compose config läuft fehlerfrei
  • Healthcheck wird nach Start healthy – erst danach TRAEFIK_ENABLE=true

7. Ergänzungen (25.10.2026)

7.1 sultan-Variante (Traefik auf anderer VM)

Gilt für Hosts, deren Traefik woanders läuft und per sultan angebunden ist.

  • Kein externes Netz proxy/traefik-net, kein Label traefik.docker.network.
  • Web-Service veröffentlicht seinen Port: ports: ["${APP_PORT}:<intern>"] (Bindung 0.0.0.0; 127.0.0.1 verwirft der sultan-Agent).
  • Label traefik.http.routers.<name>.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure} gehört dazu; sultan prüft Entrypoint-/Resolver-Namen nicht.
  • loadbalancer.server.port = Port im Container (der Agent rechnet auf den Host-Port um).
  • Namen: kein doppelter Unterstrich __ (Trenner bei sultan).
  • Wartung: docker compose stop → Wartungsseite; down → 404. Für Updates up -d (ersetzt Container) ist unkritisch.

7.2 Stacks mit mehreren Services / Datenbank

  • Datenbanken als eigener Stack (postgres), nie veröffentlicht. Der Stack legt ein benanntes Netz an (postgres_net); Anwendungen hängen es als external: true an. Je Anwendung eigene Datenbank + eigener User.
  • Innerhalb eines Stacks: depends_on mit condition: service_healthy.
  • Anwendung braucht Internet/Port → zusätzlich Netz default.

7.3 .env: Erzeugung und Geheimnisse

  • .env.example ist die einzige Quelle der Schlüssel. Platzhalter: @@SECRET@@ → beim ersten Deployment zufällig erzeugt (je Schlüssel eigener Wert); <…> → vom Betreiber zu setzen.
  • Die echte .env erzeugt Ansible nur, wenn sie fehlt; danach gehört sie dem Betreiber (kein Überschreiben). Später ersetzen Vault-Einträge die Werte (rules/secrets.md).
  • Werte, die mehrere Stacks brauchen (DB-Passwort), stehen in der .env der Anwendung; Ansible liest sie dort und legt damit DB/User an (über stdin, nie als Argument).

7.4 Deployment per Ansible

  • Rolle compose_stack (ansible/roles/compose_stack): Stack-Verzeichnis <STACK_BASE>/<stack>/, Dateien aus stacks/<stack>/, Bind-Mount-Ziele mit Eigentümer/Rechten, .env (s. 7.3), community.docker.docker_compose_v2 mit wait.
  • Datenbank je Anwendung: Rolle pg_app_db.

7.5 Rechte, Dateisystem

  • Bind-Mount-Ziele mit dem Eigentümer des Containers anlegen (z. B. 1000:1000); im Stack-README dokumentieren.
  • Auf btrfs: Datenbank-Verzeichnisse vor dem ersten Start chattr +C (kein Copy-on-Write, weniger Fragmentierung; Prüfsummen entfallen dort).

7.6 Backup & Update

  • Datenbanken per Dump sichern (pg_dumpall/pg_dump vor dem Borgmatic-Lauf) – das reine Kopieren des Datenverzeichnisses im Betrieb ist kein konsistentes Backup.
  • Update: neuen Tag in .env → Backup → docker compose pull && docker compose up -d. Rückweg: alter Tag + Restore. Kein automatisches docker system prune -a – das alte Image bleibt für den schnellen Rückweg.

Festgelegte Werte

<STACK_BASE>, TRAEFIK_NETWORK, TRAEFIK_CERTRESOLVER, GELF_HOST/GELF_PORT stehen in ai/context/environment.md.

Änderungsprotokoll

  • 07.10.2026 – übernommen und verallgemeinert (Mandantenwerte → Platzhalter, Header-Pflicht ergänzt).
  • 23.10.2026 – Mandantenbezug entfernt, Werte nur noch in environment.md; Verweis auf web_search.md korrigiert.
  • 25.10.2026 – § 7: sultan-Variante, Mehrfach-Services/DB, .env-Erzeugung (@@SECRET@@), Deployment per Ansible, Rechte/btrfs, Backup/Update ohne Prune – Entscheidungen des Betreibers vom 23.–25.10.2026.