Files
Wabenhain/docker-compose.yml
T
Nick SzaunigandClaude Opus 5 479ace1b88 docs: Konfigurationsvorlagen vollstaendig und uebernahmefaehig machen
01-configmap.yaml und 02-secret.example.yaml sind jetzt so geschrieben, dass
sie sich als Produktivkonfiguration uebernehmen lassen: in drei Abschnitte
gegliedert (ANPASSEN / Vorgaben / optional), jeder Wert mit der Angabe, was
ohne ihn nicht funktioniert. Anzupassen sind genau zwei Werte in der
ConfigMap und die mit "CHANGE-ME" markierten im Secret; alles Uebrige traegt
eine brauchbare Vorgabe oder darf leer bleiben.

Beide Dateien decken zusammen jede Umgebungsvariable ab, die die Anwendung
liest, und jeder Schluessel steht in genau einer der beiden. Bisher war das
Handarbeit und driftete: GOOGLE_PLACES_API_KEY lag in der unverschluesselten
ConfigMap, APICORE_API_URL unnoetig im Secret.

Neu ist deshalb src/lib/k8s-manifest-env.test.ts. Der Test liest die
process.env-Zugriffe unter src/ und die Schluessel der beiden Manifeste und
prueft vier Zusagen: keine gelesene Variable fehlt, kein Schluessel steht
doppelt (sonst entschiede allein die Reihenfolge der envFrom-Eintraege,
welcher Wert gewinnt), kein Schluessel ist tot, und die committete
Secret-Vorlage enthaelt keine ausgefuellten Werte.

Beim ersten Lauf hat der Test zwei Dinge gefunden. ADMIN_EMAIL und
ADMIN_PASSWORD liest nicht die App, sondern prisma/seed.ts — das ist
legitim und steht jetzt begruendet in einer Ausnahmeliste, zusammen mit
DB_PASSWORD fuer das Datenbank-StatefulSet. STRIPE_PUBLISHABLE_KEY dagegen
wird von keiner einzigen Code-Stelle gelesen: der Checkout laeuft
vollstaendig serverseitig ueber eine Stripe-Checkout-Session. Der Schluessel
ist aus Secret-Vorlage, .env.example, docker-compose.yml und README
entfernt, mit Hinweis, wofuer man ihn braeuchte.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 03:06:18 +02:00

135 lines
5.5 KiB
YAML

services:
db:
image: postgres:16.15-alpine
restart: unless-stopped
# Nur relevant, wenn DATABASE_PROVIDER=postgresql (Standard) — bei mysql
# stattdessen "docker compose --profile mysql up -d db-mysql" starten,
# siehe README, Abschnitt "Datenbank-Provider wechseln".
environment:
POSTGRES_USER: ${POSTGRES_USER:-wabenhain}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD muss in .env gesetzt sein}
POSTGRES_DB: ${POSTGRES_DB:-wabenhain}
ports:
# Nur an localhost binden statt an alle Interfaces — die App erreicht
# die DB ueber das interne Compose-Netzwerk, dieser Port dient nur
# lokalem Debugging (z.B. per psql/DB-GUI vom selben Host aus).
- "127.0.0.1:5432:5432"
volumes:
- wabenhain_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-wabenhain}"]
interval: 5s
timeout: 5s
retries: 10
# Alternative zu "db": nur starten, wenn DATABASE_PROVIDER=mysql in .env
# gesetzt ist ("docker compose --profile mysql up -d db-mysql"). Laeuft
# nicht automatisch mit "docker compose up" mit, um den Postgres-Standardfall
# nicht zu verlangsamen.
db-mysql:
image: mysql:8.4.11
profiles: ["mysql"]
restart: unless-stopped
environment:
MYSQL_USER: ${MYSQL_USER:-wabenhain}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?MYSQL_PASSWORD muss in .env gesetzt sein, falls DATABASE_PROVIDER=mysql}
MYSQL_DATABASE: ${MYSQL_DATABASE:-wabenhain}
MYSQL_ALLOW_EMPTY_PASSWORD: "no"
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
ports:
- "127.0.0.1:3306:3306"
volumes:
- wabenhain_db_mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER:-wabenhain} -p${MYSQL_PASSWORD}"]
interval: 5s
timeout: 5s
retries: 10
# Einziger nach aussen exponierter Dienst: terminiert die Client-Verbindung
# und setzt X-Forwarded-For dabei neu (verwirft einen vom Client selbst
# mitgeschickten Wert, siehe Caddyfile-Kommentar). Ohne diesen Proxy koennte
# "app" direkt erreicht werden und jedes Rate-Limit (Login, 2FA, Checkout)
# liesse sich durch einen gefaelschten X-Forwarded-For-Header umgehen.
proxy:
image: caddy:2.11.4-alpine
restart: unless-stopped
depends_on:
- app
ports:
- "80:80"
# Fuer echtes HTTPS mit eigener Domain zusaetzlich freigeben und
# SITE_ADDRESS in .env auf die Domain setzen (siehe Caddyfile):
# - "443:443"
# - "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- wabenhain_caddy_data:/data
environment:
SITE_ADDRESS: ${SITE_ADDRESS:-}
app:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
depends_on:
db:
condition: service_healthy
required: false
expose:
- "3000"
# Prueft denselben Endpunkt wie die Kubernetes-Readiness-Probe (siehe
# k8s/05-app-deployment.yaml): nicht nur "Prozess laeuft", sondern "App
# erreicht die Datenbank". start_period deckt den Migrations-/Seed-Lauf
# ab, den der Container laut Dockerfile-CMD vor dem Serverstart ausfuehrt.
healthcheck:
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:3000/api/health/ready').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
interval: 30s
timeout: 10s
retries: 3
start_period: 90s
volumes:
# Persistiert hochgeladene Admin-Bilder/-Videos (Dockerfile legt
# /app/uploads bereits mit passendem Besitzer fuer den non-root
# "nextjs"-User an) ueber Container-Neustarts/-Redeploys hinweg — ohne
# dieses Volume waere jeder Upload nach dem naechsten "docker compose
# up" wieder weg.
- wabenhain_uploads_data:/app/uploads
environment:
# Muss zum Volume-Mount oben passen — siehe .env.example.
UPLOAD_DIR: /app/uploads
# DATABASE_URL kommt komplett aus .env — je nach DATABASE_PROVIDER
# entweder die Postgres- ("db"-Service, Port 5432) oder die
# MySQL-Verbindungszeichenkette ("db-mysql"-Service, Port 3306).
DATABASE_PROVIDER: ${DATABASE_PROVIDER:-postgresql}
DATABASE_URL: ${DATABASE_URL}
# Nur der geheime Schluessel: der Checkout laeuft vollstaendig
# serverseitig, ein veroeffentlichbarer pk_-Schluessel wird nirgends
# gelesen (siehe .env.example).
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
# Nur fuer den Seed-Schritt relevant (legt das initiale Admin-Konto an,
# siehe README "Admin-Zugang") — kein Basic-Auth-Geheimnis mehr. Bewusst
# OHNE Default-Wert: ein vorhersehbarer Wert wie "[email protected]"
# liesse sich vor dem ersten Deploy-Seed-Lauf selbst registrieren, siehe
# prisma/seed.ts fuer die zusaetzliche Absicherung dagegen.
ADMIN_EMAIL: ${ADMIN_EMAIL:?ADMIN_EMAIL muss in .env gesetzt sein}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?ADMIN_PASSWORD muss in .env gesetzt sein}
SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET muss in .env gesetzt sein}
GOOGLE_PLACES_API_KEY: ${GOOGLE_PLACES_API_KEY}
GOOGLE_PLACE_ID: ${GOOGLE_PLACE_ID}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_FROM: ${SMTP_FROM:-Wabenhain <[email protected]>}
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3000}
NODE_ENV: production
volumes:
wabenhain_db_data:
wabenhain_db_mysql_data:
wabenhain_caddy_data:
wabenhain_uploads_data: