Files
Wabenhain/docker-compose.yml
T
Nick SzaunigandClaude Opus 5.5 11910dd6dd
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m25s
refactor(mail): send via the SSO app registration, drop SMTP basic auth
Mail now uses the same Entra ID app registration as "Sign in with
Microsoft" (ENTRA_TENANT_ID, ENTRA_CLIENT_ID, ENTRA_CLIENT_SECRET), so
only one enterprise application is needed. The app additionally gets
the SMTP.SendAsApp permission; Exchange still limits it to the mailbox
granted via Add-MailboxPermission.

Password (basic auth) sending is removed entirely: SMTP_HOST, SMTP_PORT,
SMTP_PASSWORD and the separate SMTP_OAUTH_* settings are gone. The
transport is fixed to smtp.office365.com:587 with required STARTTLS.
SMTP_USER (sending mailbox) and SMTP_FROM remain.

- lib/entra.ts exposes readEntraAppCredentials(), shared by SSO and mail
- diagnosis drops basic-auth-only causes; hints name the ENTRA_* values
  and warn that an expired secret breaks sign-in and mail together
- admin e-mail test, .env.example, docker-compose, k8s manifests,
  preflight and README updated

Verified against Entra ID with fake values: token error classified,
client secret never exposed, missing ENTRA_CLIENT_SECRET reported.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-26 19:23:05 +02:00

139 lines
5.8 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}
STRIPE_PUBLISHABLE_KEY: ${STRIPE_PUBLISHABLE_KEY:-}
# 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}
# Mailversand ueber Microsoft 365 mit OAuth 2.0 — dieselbe
# App-Registrierung wie "Mit Microsoft anmelden" (siehe .env.example).
SMTP_USER: ${SMTP_USER}
SMTP_FROM: ${SMTP_FROM:-Wabenhain <[email protected]>}
ENTRA_TENANT_ID: ${ENTRA_TENANT_ID}
ENTRA_CLIENT_ID: ${ENTRA_CLIENT_ID}
ENTRA_CLIENT_SECRET: ${ENTRA_CLIENT_SECRET}
ENTRA_REDIRECT_URI: ${ENTRA_REDIRECT_URI}
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: