Files
betreuungs-cockpit/docker-compose.yml
T
Florian Wilde b2ac13e128 feat: Versionierung mit Changelog, Version 1.0.0
Bis jetzt stand nirgends, welcher Stand laeuft. Beim Aktualisieren auf
einem Server ist das die erste Frage.

CHANGELOG.md nach Keep a Changelog, Versionsnummern nach SemVer. Der
Changelog sagt, was sich beim Arbeiten aendert - nicht, welche Datei
angefasst wurde. Dazu eine Tabelle, was Major, Minor und Patch fuer den
Betreiber praktisch bedeuten, und der Hinweis, dass sich eine
Datenbank-Migration nicht zurueckrollen laesst.

Die Version steht bewusst an zwei Stellen: package.json und
src/lib/version.ts. Ein Import der package.json waere im
Standalone-Build eine andere Datei als die des Projekts - die Zahl haenge
dann an der Laufzeit. Ein Test haelt beide zusammen, und
"npm run version" setzt sie gemeinsam samt Changelog-Abschnitt.

Committet und getaggt wird nicht automatisch. Ein Changelog-Eintrag, den
niemand gegengelesen hat, ist keinen Deut besser als keiner.

Sichtbar wird die Version unten in der Seitenleiste und unter
/api/gesundheit - dort mit Git-Stand, den der Docker-Build hineinreicht.
Auf der Anmeldeseite steht sie nicht: wer nicht angemeldet ist, muss
nicht wissen, welche Fassung hier laeuft.

Etappe: 8
Phase: Haertung
2026-08-22 10:41:33 +02:00

161 lines
6.1 KiB
YAML

# =============================================================================
# Betreuungs-Cockpit - Produktivbetrieb
# =============================================================================
#
# docker compose up -d --build
#
# Kein Reverse Proxy in dieser Datei: der ist auf dem Server bereits
# vorhanden. Die Anwendung horcht nur auf 127.0.0.1:3000 - von aussen
# erreichbar wird sie ausschliesslich ueber den vorhandenen Proxy, der
# auch TLS beendet. Siehe README, Abschnitt "Reverse Proxy".
#
# Die Datenbank hat bewusst KEINE Portfreigabe. Sie ist nur ueber das
# interne Docker-Netz erreichbar. Wer mit psql heran muss, geht ueber
# docker compose exec db psql -U cockpit betreuungs_cockpit
# =============================================================================
name: betreuungs-cockpit
services:
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-cockpit}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD fehlt in der .env}
POSTGRES_DB: ${POSTGRES_DB:-betreuungs_cockpit}
# Deutsche Sortierung und Zeichensatz - wichtig fuer Namenslisten
# und fuer die Volltextsuche mit der Konfiguration "german".
POSTGRES_INITDB_ARGS: '--encoding=UTF8 --locale=de_DE.UTF-8'
LANG: de_DE.UTF-8
volumes:
- postgres-daten:/var/lib/postgresql/data
- ./backups:/backups
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER:-cockpit} -d ${POSTGRES_DB:-betreuungs_cockpit}']
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
networks: [intern]
# Kein "ports:" - die Datenbank ist von aussen nicht erreichbar.
minio:
# S3-kompatibler Objektspeicher fuer die Dokumente. Wer einen externen
# Speicher in der EU nutzt (z.B. Hetzner Object Storage), kann diesen
# Dienst weglassen und in der .env S3_ENDPOINT darauf zeigen lassen.
# Auch dann gilt: AV-Vertrag nach Art. 28 DSGVO abschliessen.
image: minio/minio:RELEASE.2025-01-20T14-49-07Z
restart: unless-stopped
command: server /daten --console-address ":9001"
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:?S3_ACCESS_KEY_ID fehlt in der .env}
MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:?S3_SECRET_ACCESS_KEY fehlt in der .env}
MINIO_BROWSER_REDIRECT_URL: ${MINIO_KONSOLE_URL:-http://127.0.0.1:9001}
volumes:
- minio-daten:/daten
healthcheck:
test: ['CMD', 'mc', 'ready', 'local']
interval: 15s
timeout: 5s
retries: 10
start_period: 20s
ports:
# Nur an die Loopback-Adresse gebunden. Die Konsole erreichst du
# ueber einen SSH-Tunnel oder ueber deinen vorhandenen Proxy.
- '127.0.0.1:9001:9001'
networks: [intern]
app:
build:
context: .
dockerfile: Dockerfile
args:
# In der Oberflaeche und unter /api/gesundheit sichtbar. Setzen mit:
# GIT_COMMIT=$(git rev-parse --short HEAD) docker compose up -d --build
GIT_COMMIT: ${GIT_COMMIT:-}
BUILD_ZEIT: ${BUILD_ZEIT:-}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
minio:
condition: service_healthy
environment:
NODE_ENV: production
DATABASE_URL: postgresql://${POSTGRES_USER:-cockpit}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-betreuungs_cockpit}?schema=public&connection_limit=10
AUTH_SECRET: ${AUTH_SECRET:?AUTH_SECRET fehlt in der .env}
AUTH_URL: ${AUTH_URL:?AUTH_URL fehlt in der .env}
AUTH_TRUST_HOST: 'true'
ENC_KEY_V1: ${ENC_KEY_V1:?ENC_KEY_V1 fehlt in der .env - ohne ihn sind die verschluesselten Felder nicht lesbar}
ENC_KEY_V2: ${ENC_KEY_V2:-}
ENC_KEY_AKTIV: ${ENC_KEY_AKTIV:-1}
INDEX_KEY: ${INDEX_KEY:?INDEX_KEY fehlt in der .env}
SESSION_MAX_AGE: ${SESSION_MAX_AGE:-1800}
REAUTH_MAX_AGE: ${REAUTH_MAX_AGE:-300}
LOGIN_MAX_VERSUCHE: ${LOGIN_MAX_VERSUCHE:-5}
LOGIN_FENSTER_SEKUNDEN: ${LOGIN_FENSTER_SEKUNDEN:-900}
LOGIN_SPERRE_SEKUNDEN: ${LOGIN_SPERRE_SEKUNDEN:-900}
SPEICHER_TREIBER: s3
S3_ENDPOINT: ${S3_ENDPOINT:-http://minio:9000}
S3_REGION: ${S3_REGION:-eu-central}
S3_BUCKET: ${S3_BUCKET:-betreuungs-cockpit}
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID}
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY}
UPLOAD_MAX_BYTES: ${UPLOAD_MAX_BYTES:-26214400}
OCR_AKTIV: ${OCR_AKTIV:-true}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: ${SMTP_FROM:-}
TZ: Europe/Berlin
ports:
# Nur Loopback. Der vorhandene Reverse Proxy auf dem Host reicht
# von aussen hierher durch und beendet TLS.
- '127.0.0.1:${APP_PORT:-3000}:3000'
networks: [intern]
healthcheck:
test: ['CMD', 'node', '-e', "fetch('http://127.0.0.1:3000/api/gesundheit').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 30s
timeout: 5s
retries: 5
start_period: 40s
sicherung:
# Taeglich um 03:15 Uhr ein verschluesseltes Backup nach ./backups.
# Legt zusaetzlich die Pruefsumme ab und raeumt alte Staende auf.
image: postgres:17-alpine
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
PGHOST: db
PGUSER: ${POSTGRES_USER:-cockpit}
PGPASSWORD: ${POSTGRES_PASSWORD}
PGDATABASE: ${POSTGRES_DB:-betreuungs_cockpit}
SICHERUNG_PASSWORT: ${SICHERUNG_PASSWORT:?SICHERUNG_PASSWORT fehlt in der .env}
SICHERUNG_AUFBEWAHRUNG_TAGE: ${SICHERUNG_AUFBEWAHRUNG_TAGE:-30}
TZ: Europe/Berlin
volumes:
- ./backups:/backups
- ./scripts/sicherung.sh:/sicherung.sh:ro
- ./scripts/wiederherstellung.sh:/wiederherstellung.sh:ro
entrypoint: ['/bin/sh', '-c']
command:
- |
apk add --no-cache openssl coreutils >/dev/null
echo "15 3 * * * /bin/sh /sicherung.sh >> /backups/sicherung.log 2>&1" > /etc/crontabs/root
echo "Sicherungsdienst bereit. Naechster Lauf 03:15 Uhr."
crond -f -l 8
networks: [intern]
volumes:
postgres-daten:
minio-daten:
networks:
intern:
driver: bridge