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>
11 KiB
11 KiB
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 inai/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.envnichtTRAEFIK_ENABLE=truesteht, 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 dannTRAEFIK_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.extendsaus gemeinsamertraefik.yml– technisch ok, vom Bediener abgelehnt: Labels gehören sichtbar und manuell anpassbar ins Stackfile.- Separate
.traefik.envperCOMPOSE_ENV_FILESin.env– wirkt nicht; nur als Shell-Variable oder per--env-filebei jedem Aufruf → fehleranfällig.
4. Hinweise zur Umsetzung
extra_hostsin.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/curlim 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: .envreicht 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:
- Zweck und Zugriff (FQDN, ggf. Port).
- Orig-Doku: Links zu Image-Seite (Docker Hub / GHCR) und Hersteller-Doku, Abrufdatum.
- ENV-Variablen laut Orig-Doku: Tabelle
Variable | Bedeutung | Default | gesetzt in .env?– vollständig aus der Herstellerdoku übernommen, nicht geraten. - Volumes/Pfade laut Orig-Doku (welcher Container-Pfad persistiert was).
- Ports intern/extern, Healthcheck-Endpunkt und Begründung.
- 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.examplevollständig, ohne Secrets- README mit Orig-Doku und ENV-Tabelle
docker compose configläuft fehlerfrei- Healthcheck wird nach Start
healthy– erst danachTRAEFIK_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 Labeltraefik.docker.network. - Web-Service veröffentlicht seinen Port:
ports: ["${APP_PORT}:<intern>"](Bindung0.0.0.0;127.0.0.1verwirft 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 Updatesup -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 alsexternal: truean. Je Anwendung eigene Datenbank + eigener User. - Innerhalb eines Stacks:
depends_onmitcondition: service_healthy. - Anwendung braucht Internet/Port → zusätzlich Netz
default.
7.3 .env: Erzeugung und Geheimnisse
.env.exampleist 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
.enverzeugt 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
.envder 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 ausstacks/<stack>/, Bind-Mount-Ziele mit Eigentümer/Rechten,.env(s. 7.3),community.docker.docker_compose_v2mitwait. - 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_dumpvor 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 automatischesdocker 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 aufweb_search.mdkorrigiert. - 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.