Vor der Generierung wird für jedes Image die aktuelle Doku via Context7, Docker Hub und GitHub-Releases konsultiert: Pflicht-Env-Vars der aktuellen Major-Version, Breaking Changes, deprecated Variablen, Default-Ports und Volume-Pfade. Ergebnis landet als Header-Kommentar in der Compose-Datei. Bei kritischen Stacks (DBs, Auth) wird die Major-Version gepinnt statt :latest, um unkontrollierte Major-Upgrades zu vermeiden. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
13 KiB
description, model
| description | model |
|---|---|
| Erstellt oder aktualisiert eine docker-compose.yaml nach den Best Practices dieses Workflows — aktuelle Compose-Syntax, explizite Konfiguration, Secrets in .env, optionales VPN-Binding für den Webserver. Aktivieren wenn ein neuer Container-Stack aufgesetzt oder ein bestehender überarbeitet werden soll. Nicht verwenden für reine Image-Updates, einzelne docker-Befehle oder Dockerfile-Änderungen. | claude-opus-4-6 |
/docker-compose — Compose-Stack nach Hausregeln
Zielort: ein Verzeichnis unter /docker/<name>/ auf einem der Hosts oder lokal. Der Skill liefert eine docker-compose.yaml plus passende .env-Vorlage.
Phase 0: Aufgabenklärung
Stelle dem User folgende Fragen, sofern nicht schon im Request beantwortet:
-
Name des Stacks (Verzeichnisname unter
/docker/) -
Welche Services sind enthalten (Image + Zweck)?
-
Zielhost — eine der folgenden vier Optionen, per
AskUserQuestion:Ziel SSH-Pfad Netzwerk-Kontext automationssh automation:/docker/<name>/internes LAN, kein VPN-Binding controllerssh controller:/docker/<name>/internes LAN, kein VPN-Binding webserverssh waldperle:/docker/<name>/öffentlich, interne Ports an VPN 10.7.0.1bindenlocalhostaktuelles Arbeitsverzeichnis kein Bind-Prefix -
Reverse-Proxy — soll der Stack über
nginx-proxy-managerans Web? Dannreverse-proxy-Netz nötig. -
Persistente Daten — welche Verzeichnisse müssen über Restarts/Recreates überleben (Datenbankdaten, Configs, Uploads, Logs)?
-
Gewünschte Host-Ports — welche Ports sollen extern erreichbar sein? (werden in Phase 1.5 gegen Belegung geprüft)
Bei webserver zusätzlich:
"Sollen die Ports nur über das VPN (
10.7.0.1) erreichbar sein oder auch öffentlich (z.B.80,443)?"
Standardregel: alle Ports an 10.7.0.1 binden, nur explizit öffentliche (HTTP/HTTPS für reverse-proxy) bleiben ohne Bind-IP.
Phase 0.5: Container-Recherche (Pflicht vor Generierung)
Für jedes Image im Stack die aktuelle Dokumentation konsultieren, bevor die Compose-Datei geschrieben wird. Ziel: korrekte aktuelle Konfiguration, bekannte Breaking Changes berücksichtigen, deprecated Variablen vermeiden.
Quellen (in dieser Reihenfolge)
- Context7 (bevorzugt für offizielle Images):
mcp__plugin_context7_context7__resolve-library-id → Library-ID finden mcp__plugin_context7_context7__query-docs → gezielte Frage stellen - Offizielle Docker-Hub-Seite via
WebFetch:https://hub.docker.com/_/<image> # Official Images https://hub.docker.com/r/<vendor>/<image> # Community Images - GitHub-Releases / CHANGELOG via
WebFetch— für Breaking Changes der aktuellen Major-Version. - WebSearch als Fallback, wenn die obigen Quellen leer bleiben.
Was muss konkret geprüft werden?
Pro Image diese Punkte abklären:
- Aktuelle Major-Version (
:latestzeigt auf welche?) - Breaking Changes seit der vorherigen Major-Version (z.B. PostgreSQL-Daten-Migration zwischen Major-Versionen, Keycloak
start-devvsstart --optimized, MySQL→MariaDB-Replacements) - Pflicht-Env-Vars der aktuellen Version (z.B. Auth-Defaults wurden in vielen Images entfernt —
POSTGRES_PASSWORDist Pflicht, kein Standard mehr) - Deprecated Env-Vars — alte Namen vermeiden
- Default-Ports und ob sie sich geändert haben
- Volume-Pfade des Images — wo erwartet das Image persistente Daten?
- Health-Check-Kommando — was empfiehlt der Maintainer?
- User/UID-Verhalten — läuft das Image als Root, gibt es ein
PUID/PGID-Pattern, oderuser:-Override möglich?
Output dieser Phase
Bevor weitergegangen wird, dem User eine Zusammenfassung pro Image zeigen:
postgres:latest → aktuell 17.x
Breaking: pg_dump-Format zwischen Major-Versionen inkompatibel — Major-Upgrade
erfordert pg_dumpall/restore (siehe Release-Notes)
Pflicht: POSTGRES_PASSWORD
Empfohlen: POSTGRES_DB, POSTGRES_USER, Healthcheck pg_isready
Volume: /var/lib/postgresql/data
Bei Major-Sprung gegenüber einem bestehenden Stack: User explizit warnen und Migrationsschritt vorschlagen, bevor :latest gepinnt bleibt.
Im Compose-Header dokumentieren
Header-Kommentar in der docker-compose.yaml:
# Stack: <name>
# Recherchiert: <YYYY-MM-DD>
# Images:
# postgres:latest → 17.x (Breaking: Major-Upgrade braucht pg_dumpall)
# <image>:latest → x.y (Hinweise/Breaking)
So bleibt nachvollziehbar, gegen welchen Wissensstand der Stack designed wurde.
Phase 1.5: Port-Belegungs-Analyse (Pflicht vor Generierung)
Bevor irgendein Port in die docker-compose.yaml geschrieben wird, beide Prüfungen auf dem Zielhost ausführen:
Prüfung 1: Alle bestehenden Compose-Files
# Bei automation/controller/webserver via SSH:
ssh <host> "grep -rhE '^\s*-\s*\"?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:)?[0-9]+:[0-9]+' /docker/ 2>/dev/null \
| sed -E 's/.*\"?([0-9.]+:)?([0-9]+):.*/\2/' | sort -u"
# Bei localhost: lokal ausführen (Pfad ggf. anpassen)
grep -rhE '^\s*-\s*\"?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:)?[0-9]+:[0-9]+' . 2>/dev/null \
| sed -E 's/.*\"?([0-9.]+:)?([0-9]+):.*/\2/' | sort -u
Prüfung 2: Aktive Listener auf dem Host
ssh <host> "ss -tlnp 2>/dev/null | awk 'NR>1 {split(\$4, a, \":\"); print a[length(a)]}' | sort -u"
# Bei localhost:
ss -tlnp 2>/dev/null | awk 'NR>1 {split($4, a, ":"); print a[length(a)]}' | sort -u
Konfliktauflösung
Vergleiche die in Phase 0 gewünschten Host-Ports mit beiden Listen:
- Frei in beiden: Port übernehmen.
- Belegt in Prüfung 1 (Compose): zeigen, welcher Stack/Service belegt — User fragen, ob Konflikt akzeptiert ist (selten korrekt) oder ein anderer Port gewählt werden soll.
- Belegt in Prüfung 2 (Listener): zeigen welcher Prozess — alternativen Port vorschlagen.
- Vorschlag bei Konflikt: nächste freie Port-Nummer aus passendem Bereich (Web-UIs
8000–8999, DBs5400+, Custom3000–3999).
Erst wenn alle gewünschten Ports konfliktfrei sind, weiter zu Phase 3.
Hinweis für webserver: das VPN-IP-Prefix 10.7.0.1 ändert nichts an der Belegungsprüfung — ein bereits gebundener Port ist gebunden, egal an welche IP.
Phase 1: Pflichtregeln (nicht verhandelbar)
Diese Regeln gelten für jeden generierten Stack:
Compose-Syntax
- Kein
version:-Key. Compose v2 ignoriert ihn — nicht aufnehmen. name: <stack>als oberstes Feld (Project-Name explizit setzen).- YAML-Mapping-Stil konsistent pro Datei (entweder Listen-Stil
- KEY=VALoder Map-StilKEY: VAL— nicht mischen innerhalb eines Services).
Pro Service
container_name: <name>— explizit, stabile Namen für Logs/Scripts.hostname: <name>— für interne Service-Adressierung in geteilten Netzwerken.restart: unless-stoppedals Default.alwaysnur wenn der Container nach einem manuellendocker stopvon alleine wieder hochkommen soll.environment:TZ=Europe/Berlinist Pflicht.- Alle relevanten Env-Variablen explizit auflisten (keine impliziten Defaults aus dem Image — der Image-Default kann sich ändern).
- Sensible Werte (Passwörter, Tokens, Secrets, API-Keys) ausschließlich als
${VARIABLE}referenzieren — Werte in.env. - Keine Inline-Defaults vom Typ
${VAR:-default}für Secrets. Für nicht-sensible Werte erlaubt, aber sparsam.
logging:5 MB pro Datei × 3 Dateien = max. 15 MB pro Container.logging: driver: "json-file" options: max-size: "5M" max-file: "3"labels:Defaultlabels: - "com.centurylinklabs.watchtower.enable=false"false(entspricht globaler CLAUDE.md). Auftruesetzen, wenn der Container über:latestläuft und automatische Updates erwünscht sind.user: "1000:1000"als Default. Ausnahmen nur, wenn das Image Root erfordert (z.B. NPM Port 80/443) — dann mit Kommentar warum.- Volumes als relative Bind-Mounts (
./data:/data). Niemals absolute Pfade ausserhalb von./, niemals named volumes für Datenpersistenz (Backup-Strategie hängt am Bind-Mount). - Read-only mounten wo möglich (
./config:/etc/app/config:ro).
Ports
- Immer als gequoteter String:
"8080:80". Unquoted YAML kann5060:5060als Sexagesimalzahl interpretieren. - Pro Zielhost:
automation,controller,localhost: kein IP-Prefix (LAN-intern erreichbar).webserver(waldperle): alle internen Ports an10.7.0.1binden —"10.7.0.1:8080:80". Nur explizit öffentliche Ports (NPM 80/443) ohne IP-Prefix.
- Belegung muss in Phase 1.5 geprüft sein — kein Port ohne vorherige Konfliktprüfung.
Netzwerke
- Externe Standard-Netze:
reverse-proxy— für alle Container, die über NPM ans Web sollenpublic— für Container mit direktem Public-Exposureopenhab— Smart-Home-Stack (nurautomation)
- Externe Netze in der
networks:-Sektion immer mitexternal: truemarkieren. - Stack-interne Netze ohne
externaldeklarieren (oder weglassen — Compose erzeugt dann<name>_default).
Multi-Service-Stacks
- Healthchecks für DB/Cache:
healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s timeout: 5s retries: 5 depends_onmitcondition: service_healthywo Healthchecks existieren — verhindert Race-Conditions beim Start.
Image-Versionen
:latestist der Default (User-Wunsch: immer neueste Version) — aber nur, wenn die in Phase 0.5 dokumentierte Major-Version mit der Stack-Konfiguration kompatibel ist.- Bei
:latest+ Watchtower-Enable=true→ automatische Updates. Bei Datenbanken nur, wenn das Image rolling-kompatibel ist (oft nicht — siehe PostgreSQL-Hinweis aus Phase 0.5). - Bei kritischen Stacks (Datenbanken, Auth) Major-Version pinnen (z.B.
postgres:17) und Major-Upgrades bewusst durchführen.
Security-Hardening (für exposed Services empfohlen)
cap_drop:
- ALL
security_opt:
- no-new-privileges=true
Aufnehmen für: Web-UIs, REST-APIs, alles was Ports öffnet. Nicht für DBs/Caches im internen Netz (würde dort oft brechen).
.env-Datei
- Liegt im selben Verzeichnis wie die
docker-compose.yaml. - Wird per
.gitignoreausgeschlossen. - Eine
.env.exampleohne Werte mitliefern — als Vorlage. - Format:
# Pflicht DB_USER= DB_PASSWORD= # ...
Phase 2: VPN-Binding für Webserver-Stacks
Wenn der User in Phase 0 als Zielhost webserver (waldperle) gewählt hat:
-
Bind-Adresse abfragen falls nicht bekannt:
"Welche VPN-Interface-IP soll gebunden werden? (Standard
waldperle:10.7.0.1)" -
Pro Service-Port entscheiden:
- Public (im Internet erreichbar, typisch Reverse-Proxy 80/443): ohne IP-Prefix.
- Alle anderen Ports (Web-UIs, DBs, Admin-Interfaces, Metrics): an die VPN-IP binden.
-
Begründung im Kopf des Compose-Files als Kommentar dokumentieren:
# Webserver-Stack — interne Ports nur via VPN (10.7.0.1) erreichbar. # Nur 80/443 (Reverse-Proxy) sind öffentlich exponiert.
Phase 3: Generierung
3a: Verzeichnis-Layout
<stack-name>/
├── docker-compose.yaml
├── .env (lokal, nicht in Git)
├── .env.example (Vorlage in Git)
├── .gitignore (mind. .env, dazu Daten-Verzeichnisse)
└── <persistente Verzeichnisse, z.B. ./data, ./db, ./config>
3b: Datei erzeugen
Generiere die docker-compose.yaml nach den Regeln aus Phase 1 + 2. Vor dem Schreiben prüfen, ob die Datei schon existiert — bei Update nur die nötigen Felder anfassen, vorhandene Kommentare/Strukturen erhalten.
3c: Persistente Verzeichnisse anlegen
Nur als Hinweis an den User — nicht selbst per mkdir ausführen. Beispiel:
"Vor dem ersten Start:
mkdir -p ./data ./db && chown -R 1000:1000 ./data ./db"
3d: .env.example erzeugen
Liste alle in der Compose-Datei referenzierten ${VARIABLE} mit leerem Wert. Sortierung: erst Pflicht, dann optional. Jede Variable mit Einzeiler-Kommentar, wozu sie dient.
Phase 4: Validierung
# Auf dem Zielhost (automation/controller/webserver via SSH, oder lokal):
docker compose -f <pfad>/docker-compose.yaml config
Erfolgreich heisst: keine Warnings, kein "version is obsolete", alle ${VAR} auflösbar (mit gefüllter .env).
Bei Fehlern: korrigieren, erneut validieren.
Phase 5: Test-Empfehlung
# Trockenlauf
docker compose -f <pfad>/docker-compose.yaml config
# Start
cd <pfad> && docker compose up -d
# Logs prüfen
docker compose logs -f <service>
# Status
docker compose ps
Warte auf User-Rückmeldung, ob der Stack erfolgreich gestartet ist.
Phase 6: Abschluss via /ship
Wenn die Compose-Datei in einem Git-Repo liegt: /ship aufrufen.
Liegt sie nur lokal auf dem Host (z.B. direkt unter /docker/<name>/ auf automation/waldperle, ohne Git): User erinnern, die Konfiguration manuell ins Backup aufzunehmen.
Pitfalls — explizite Verbote
- Kein
version:-Key (obsolet). - Keine impliziten Image-Defaults für relevante Konfiguration.
- Keine ungequoteten Port-Mappings.
- Keine absoluten Pfade in Bind-Mounts.
- Keine Secrets im Klartext in der Compose-Datei.
- Keine named Volumes für persistente Daten.
- Auf
webserver(waldperle): kein ungebundener interner Port — sonst public. - Keine Port-Angabe ohne vorherige Belegungs-Analyse aus Phase 1.5.