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]>
135 lines
5.5 KiB
YAML
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:
|