No self-signup: admins create every account in Config -> Benutzer with a typed start password; a forced password change gates first use. Session- cookie auth protects all /api/* routes; /api/users additionally requires the admin role. See docs/authentication.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
111 lines
3.3 KiB
YAML
111 lines
3.3 KiB
YAML
# Server-Stack: gebauter Stand, keine Quellcode-Mounts, keine Datei-Watcher.
|
|
# docker compose up --build --detach
|
|
#
|
|
# Fuer die Entwicklung mit Hot Reload compose.dev.yaml verwenden.
|
|
name: leistungsbilanz
|
|
|
|
x-build: &build
|
|
context: .
|
|
target: prod
|
|
args:
|
|
# Wird von next build in .next/routes-manifest.json eingebacken, siehe
|
|
# Dockerfile. Muss der API-Adresse in diesem Netz entsprechen.
|
|
API_INTERNAL_URL: http://api:3000
|
|
|
|
x-service-defaults: &service-defaults
|
|
init: true
|
|
restart: unless-stopped
|
|
# Der API-Prozess beendet HTTP-Server und SQLite-Handle auf SIGTERM; die
|
|
# Frist gibt laufenden Requests Zeit, statt sie nach 10s in ein SIGKILL
|
|
# laufen zu lassen.
|
|
stop_grace_period: 20s
|
|
# Das Image laeuft als "node" (UID 1000); zusaetzliche Rechte braucht
|
|
# weder der API- noch der Web-Prozess.
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
cap_drop:
|
|
- ALL
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "20m"
|
|
max-file: "10"
|
|
|
|
services:
|
|
api:
|
|
<<: *service-defaults
|
|
build: *build
|
|
# "exec" beim letzten Schritt: sonst bleibt sh der Vordergrundprozess und
|
|
# SIGTERM erreicht node nie.
|
|
command:
|
|
- sh
|
|
- -c
|
|
- node scripts/run-migrations.js && node scripts/db-verify-circuit-schema.js && exec node dist/server/index.js
|
|
environment:
|
|
NODE_ENV: production
|
|
PORT: "3000"
|
|
LOG_LEVEL: "${LOG_LEVEL:-info}"
|
|
# AUTH_BOOTSTRAP_* legen nur den allerersten Admin an (solange die
|
|
# users-Tabelle leer ist) und gehoeren in eine lokale, nicht versionierte
|
|
# .env - siehe docs/AUTH.md.
|
|
AUTH_BOOTSTRAP_USERNAME: "${AUTH_BOOTSTRAP_USERNAME:-}"
|
|
AUTH_BOOTSTRAP_PASSWORT: "${AUTH_BOOTSTRAP_PASSWORT:-}"
|
|
AUTH_SESSION_HOURS: "${AUTH_SESSION_HOURS:-12}"
|
|
COOKIE_SECURE: "${COOKIE_SECURE:-false}"
|
|
# Nicht auf dem Host veroeffentlicht: die API wird ueber den
|
|
# Next.js-Rewrite im Web-Dienst erreicht.
|
|
expose:
|
|
- "3000"
|
|
# Harte Obergrenze statt unbegrenztem Wachstum. memswap_limit == mem_limit
|
|
# schaltet Swap fuer den Container ab, damit ein Leck den Container trifft
|
|
# und nicht den Host.
|
|
mem_limit: 512m
|
|
memswap_limit: 512m
|
|
volumes:
|
|
- ./data:/app/data
|
|
healthcheck:
|
|
test:
|
|
- CMD
|
|
- node
|
|
- -e
|
|
- fetch('http://127.0.0.1:3000/health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1))
|
|
interval: 30s
|
|
timeout: 3s
|
|
retries: 5
|
|
start_period: 20s
|
|
|
|
web:
|
|
<<: *service-defaults
|
|
build: *build
|
|
command:
|
|
- node_modules/.bin/next
|
|
- start
|
|
- -p
|
|
- "3001"
|
|
- --hostname
|
|
- 0.0.0.0
|
|
environment:
|
|
NODE_ENV: production
|
|
API_INTERNAL_URL: http://api:3000
|
|
NEXT_TELEMETRY_DISABLED: "1"
|
|
LOG_LEVEL: "${LOG_LEVEL:-info}"
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
ports:
|
|
- "3001:3001"
|
|
mem_limit: 1g
|
|
memswap_limit: 1g
|
|
healthcheck:
|
|
# /web-health statt /health: next.config.mjs leitet /health an die API
|
|
# weiter, und "/" wuerde wegen des Redirects auf /projects bei jeder
|
|
# Probe zwei Seiten rendern.
|
|
test:
|
|
- CMD
|
|
- node
|
|
- -e
|
|
- fetch('http://127.0.0.1:3001/web-health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1))
|
|
interval: 30s
|
|
timeout: 3s
|
|
retries: 5
|
|
start_period: 20s
|