Files
martinandClaude Opus 4.7 58d6ed0b1c refactor: INSTALL.md nur mit stack-spezifischen Inhalten
Die starre 8-Sektionen-Vorlage entfernt — sie enthielt viel Boilerplate
(Docker-Basics, generische compose-Befehle), der jeder Leser ohnehin kennt.
Neuer Ansatz: INSTALL.md listet nur das, was der Leser aus allgemeiner
Docker-Kenntnis nicht ableiten kann — konkrete Netze, Pfade, UIDs,
Pflicht-Env-Vars, Initial-Zugang, Reverse-Proxy-Setup, Major-Upgrade-Schritte,
stack-spezifisches Troubleshooting.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 08:06:32 +02:00

354 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: 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.
model: 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:
1. **Name des Stacks** (Verzeichnisname unter `/docker/`)
2. **Welche Services** sind enthalten (Image + Zweck)?
3. **Zielhost** — eine der folgenden vier Optionen, per `AskUserQuestion`:
| Ziel | SSH-Pfad | Netzwerk-Kontext |
|---|---|---|
| `automation` | `ssh automation:/docker/<name>/` | internes LAN, kein VPN-Binding |
| `controller` | `ssh controller:/docker/<name>/` | internes LAN, kein VPN-Binding |
| `webserver` | `ssh waldperle:/docker/<name>/` | öffentlich, interne Ports an VPN `10.7.0.1` binden |
| `localhost` | aktuelles Arbeitsverzeichnis | kein Bind-Prefix |
4. **Reverse-Proxy** — soll der Stack über `nginx-proxy-manager` ans Web? Dann `reverse-proxy`-Netz nötig.
5. **Persistente Daten** — welche Verzeichnisse müssen über Restarts/Recreates überleben (Datenbankdaten, Configs, Uploads, Logs)?
6. **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)
1. **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
```
2. **Offizielle Docker-Hub-Seite** via `WebFetch`:
```
https://hub.docker.com/_/<image> # Official Images
https://hub.docker.com/r/<vendor>/<image> # Community Images
```
3. **GitHub-Releases / CHANGELOG** via `WebFetch` — für Breaking Changes der aktuellen Major-Version.
4. **WebSearch** als Fallback, wenn die obigen Quellen leer bleiben.
### Was muss konkret geprüft werden?
Pro Image diese Punkte abklären:
- **Aktuelle Major-Version** (`:latest` zeigt auf welche?)
- **Breaking Changes** seit der vorherigen Major-Version (z.B. PostgreSQL-Daten-Migration zwischen Major-Versionen, Keycloak `start-dev` vs `start --optimized`, MySQL→MariaDB-Replacements)
- **Pflicht-Env-Vars** der aktuellen Version (z.B. Auth-Defaults wurden in vielen Images entfernt — `POSTGRES_PASSWORD` ist 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, oder `user:`-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`:
```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
```bash
# 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
```bash
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 `80008999`, DBs `5400+`, Custom `30003999`).
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=VAL` oder Map-Stil `KEY: 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-stopped`** als Default. `always` nur wenn der Container nach einem manuellen `docker stop` von alleine wieder hochkommen soll.
- **`environment:`**
- `TZ=Europe/Berlin` ist 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:`**
```yaml
logging:
driver: "json-file"
options:
max-size: "5M"
max-file: "3"
```
5 MB pro Datei × 3 Dateien = max. 15 MB pro Container.
- **`labels:`**
```yaml
labels:
- "com.centurylinklabs.watchtower.enable=false"
```
Default `false` (entspricht globaler CLAUDE.md). Auf `true` setzen, wenn der Container über `:latest` lä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 kann `5060:5060` als Sexagesimalzahl interpretieren.
- **Pro Zielhost:**
- `automation`, `controller`, `localhost`: kein IP-Prefix (LAN-intern erreichbar).
- `webserver` (waldperle): alle internen Ports an `10.7.0.1` binden — `"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 sollen
- `public` — für Container mit direktem Public-Exposure
- `openhab` — Smart-Home-Stack (nur `automation`)
- Externe Netze in der `networks:`-Sektion immer mit `external: true` markieren.
- Stack-interne Netze ohne `external` deklarieren (oder weglassen — Compose erzeugt dann `<name>_default`).
### Multi-Service-Stacks
- **Healthchecks für DB/Cache:**
```yaml
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
```
- **`depends_on` mit `condition: service_healthy`** wo Healthchecks existieren — verhindert Race-Conditions beim Start.
### Image-Versionen
- **`:latest`** ist 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)
```yaml
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 `.gitignore` ausgeschlossen.
- Eine `.env.example` ohne 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:
1. **Bind-Adresse abfragen** falls nicht bekannt:
> "Welche VPN-Interface-IP soll gebunden werden? (Standard `waldperle`: `10.7.0.1`)"
2. **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.
3. **Begründung im Kopf des Compose-Files als Kommentar dokumentieren:**
```yaml
# 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
├── INSTALL.md (Schritt-für-Schritt-Anleitung)
├── .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. Die konkreten Befehle landen in der `INSTALL.md` (3e).
### 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.
### 3e: `INSTALL.md` erzeugen (Pflicht)
Die `INSTALL.md` enthält **nur stack-spezifische Inhalte** — alles, was der Leser nicht aus der allgemeinen Docker-Compose-Kenntnis weiss. Allgemeines (was Docker ist, dass `docker compose up -d` startet, dass `.env` aus `.env.example` kopiert wird) **nicht aufnehmen**.
#### Was rein muss (sofern für den Stack zutreffend)
- **Header:** Stack-Name, Zielhost, Recherche-Datum (aus Phase 0/0.5)
- **Externe Netzwerke** dieses Stacks: konkrete Namen, die existieren müssen (`reverse-proxy`, `public`, `openhab`, …)
- **Persistente Verzeichnisse** dieses Stacks: exakte Pfade, erforderliche UID/GID, Sonderfälle (z.B. PostgreSQL braucht UID des Postgres-Users)
- **Pflicht-Env-Vars** dieses Stacks: was der Wert bedeutet, Format-Constraints, Generierungs-Hinweis (z.B. "32-Byte-Hex", "openssl rand -hex 32")
- **Initial-Zugang**: Default-Admin-Login, Setup-Wizard-URL, erstes Token aus den Logs ziehen — was dieses Image konkret macht
- **Reverse-Proxy-Einstellungen** für DIESEN Service: Ziel-Container + Port, Pfad-Besonderheiten, WebSocket-Pflicht, Trust-Proxy-Hops, spezielle Header
- **Ports und Erreichbarkeit** dieses Stacks: konkrete URLs, bei `webserver` mit `10.7.0.1` und Public-Variante
- **Backup-relevante Pfade** und stack-spezifische Pre-Backup-Schritte (z.B. `pg_dumpall` vor dem Volume-Backup)
- **Update-Besonderheiten** dieses Images (aus Phase 0.5): Major-Migrations-Skripte, deprecated Env-Vars die ersetzt werden müssen, Reihenfolge bei Multi-Service-Updates
- **Stack-spezifische Healthcheck-/Troubleshooting-Hinweise**: bekannte Fehler des Images, übliche Logmeldungen die Warnung sind aber keine Probleme, container-spezifische Reset-Schritte
#### Was raus bleibt
- Wie man Docker installiert
- Generische `docker compose`-Befehle ohne stack-spezifischen Kontext
- "cp .env.example .env" und ähnliche Standardschritte
- Allgemeine Compose-Theorie
#### Quellen für den Inhalt
- Phase 0.5 (Container-Recherche) — Breaking Changes, Pflicht-Env-Vars, Volume-Pfade
- Phase 1.5 / 2 — Ports, VPN-Binding
- Compose-Datei selbst — Bind-Mounts, Netzwerke, depends_on-Reihenfolge
- Bei Lücken: erneut Doku des Images via Context7/WebFetch konsultieren, nicht raten
Bei Update eines bestehenden Stacks: existierende `INSTALL.md` lesen, gezielt ergänzen — nicht überschreiben, keine Sektionen ohne neuen Inhalt einfügen.
## Phase 4: Validierung
```bash
# 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
```bash
# 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.