Zwei Wege in den Betrieb, beide so weit gefahren, wie es hier moeglich war. deploy/install.sh richtet Debian 13 in neun Schritten ein: PostgreSQL 17 hoert nur am Unix-Socket, der Dienstnutzer hat kein Login, die Geheimnisse entstehen im Zielsystem und liegen 0600 bei root. Die Umgebungsdatei wird nie ueberschrieben -- auch nicht beim Aktualisieren, sonst enden alle Sitzungen und die verschluesselten Bind-Passwoerter waeren unlesbar. deploy/proxmox/laufzettel.sh legt den Container an und ruft install.sh darin auf. Der pct-Teil ist ungetestet, weil kein PVE-Host zur Verfuegung stand; die Gegenprobe steht als Pruefliste in docs/betrieb/proxmox.md, das Folge-Issue ist #024. Die Abnahme gegen alle 61 Kriterien hat drei Zusagen ohne Deckung gefunden, alle behoben: der Healthcheck des Webdienstes konnte nie bestehen (busybox loest localhost nach ::1 auf, Next lauscht nur auf IPv4), die CI-Pipeline konnte in keinem Job gruen werden (kein LDAP-Dienst, kein gestarteter Server, kein shellcheck), und der README nannte fuer die Entwicklungsdatenbank den falschen Port. Closes #19
133 lines
5.2 KiB
YAML
133 lines
5.2 KiB
YAML
# Laufzettel – Betriebsstand.
|
||
#
|
||
# Die Datenbank ist bewusst von aussen NICHT erreichbar: kein ports:-Mapping und
|
||
# ein Netz ohne Gateway (internal: true). Nur web und migrate kommen ueber den
|
||
# Dienstnamen daran. Fuer die Entwicklung gibt es docker-compose.dev.yml.
|
||
#
|
||
# Sicherung trotz geschlossenem Port:
|
||
# docker compose exec -T postgres pg_dump -U laufzettel laufzettel > sicherung.sql
|
||
|
||
name: laufzettel
|
||
|
||
services:
|
||
postgres:
|
||
image: postgres:17-alpine
|
||
restart: unless-stopped
|
||
environment:
|
||
POSTGRES_USER: ${POSTGRES_USER:-laufzettel}
|
||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD fehlt in der .env}
|
||
POSTGRES_DB: ${POSTGRES_DB:-laufzettel}
|
||
# Absichtlich expose statt ports: im LAN und auf dem Host nicht erreichbar.
|
||
expose:
|
||
- "5432"
|
||
networks:
|
||
- intern
|
||
volumes:
|
||
- datenbank:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-laufzettel}"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 10
|
||
|
||
# Laeuft einmal beim Start und spielt nur die Migrationen ein.
|
||
# Der Seed wird bewusst nicht automatisch ausgefuehrt, siehe README.
|
||
migrate:
|
||
build:
|
||
context: .
|
||
target: bauen
|
||
restart: "no"
|
||
environment:
|
||
# Immer aus den Einzelteilen, **nie** aus einer DATABASE_URL der .env: dort
|
||
# steht die Adresse fuer die Entwicklung (localhost und ein offener Port),
|
||
# und im Container zeigt sie ins Leere. Wer den dokumentierten Weg ging,
|
||
# bekam einen Stand, der nicht startet — gefunden bei der Abnahme zu #019.
|
||
# Im Compose-Stand heisst die Datenbank immer `postgres`, das ist der
|
||
# Dienstname im Netz `intern`.
|
||
DATABASE_URL: postgresql://${POSTGRES_USER:-laufzettel}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-laufzettel}?schema=public
|
||
#
|
||
# Fuer die Migrationen braucht es nichts weiter — wohl aber fuer den Seed,
|
||
# der ueber denselben Dienst gefahren wird:
|
||
# `docker compose run --rm migrate pnpm db:seed` (siehe README).
|
||
#
|
||
# APP_SECRET verschluesselt das Bind-Passwort der Beispiel-Verzeichnis-
|
||
# quelle. Ohne es brach der Seed mit einem Stapelverlauf ab, und zwar
|
||
# nachdem er die Vorlagen schon angelegt hatte — gefunden bei der Abnahme
|
||
# zu #019: der dokumentierte Befehl scheiterte auf einer frischen
|
||
# Installation.
|
||
APP_SECRET: ${APP_SECRET:?APP_SECRET fehlt in der .env}
|
||
SEED_MODE: ${SEED_MODE:-INTERN}
|
||
SEED_ADMIN_EMAIL: ${SEED_ADMIN_EMAIL:-}
|
||
SEED_ADMIN_PASSWORD: ${SEED_ADMIN_PASSWORD:-}
|
||
SEED_ORG_NAME: ${SEED_ORG_NAME:-}
|
||
SEED_ORG_KUERZEL: ${SEED_ORG_KUERZEL:-}
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
networks:
|
||
- intern
|
||
command: ["pnpm", "exec", "prisma", "migrate", "deploy"]
|
||
|
||
web:
|
||
build:
|
||
context: .
|
||
target: laufzeit
|
||
args:
|
||
APP_VERSION: ${APP_VERSION:-0.1.0}
|
||
GIT_SHA: ${GIT_SHA:-}
|
||
restart: unless-stopped
|
||
environment:
|
||
# Immer aus den Einzelteilen, **nie** aus einer DATABASE_URL der .env: dort
|
||
# steht die Adresse fuer die Entwicklung (localhost und ein offener Port),
|
||
# und im Container zeigt sie ins Leere. Wer den dokumentierten Weg ging,
|
||
# bekam einen Stand, der nicht startet — gefunden bei der Abnahme zu #019.
|
||
# Im Compose-Stand heisst die Datenbank immer `postgres`, das ist der
|
||
# Dienstname im Netz `intern`.
|
||
DATABASE_URL: postgresql://${POSTGRES_USER:-laufzettel}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-laufzettel}?schema=public
|
||
APP_SECRET: ${APP_SECRET:?APP_SECRET fehlt in der .env}
|
||
APP_URL: ${APP_URL:?APP_URL fehlt in der .env}
|
||
AUTH_LOCAL_ENABLED: ${AUTH_LOCAL_ENABLED:-false}
|
||
OIDC_ISSUER: ${OIDC_ISSUER:-}
|
||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-}
|
||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-}
|
||
OIDC_NAME: ${OIDC_NAME:-authentik}
|
||
OIDC_GROUP_ADMIN: ${OIDC_GROUP_ADMIN:-}
|
||
OIDC_GROUP_EDITOR: ${OIDC_GROUP_EDITOR:-}
|
||
OIDC_GROUP_TECHNICIAN: ${OIDC_GROUP_TECHNICIAN:-}
|
||
OIDC_GROUP_VIEWER: ${OIDC_GROUP_VIEWER:-}
|
||
OIDC_CUSTOMER_GROUP_PREFIX: ${OIDC_CUSTOMER_GROUP_PREFIX:-}
|
||
# Der Verzeichnis-Abgleich laeuft im selben Prozess (#016). Hier steht er
|
||
# an, weil dieser Dienst der einzige ist, der dauerhaft laeuft.
|
||
ZEITPLAN_AKTIV: ${ZEITPLAN_AKTIV:-true}
|
||
# Standardmaessig nur auf localhost; davor gehoert ein Reverse Proxy.
|
||
ports:
|
||
- "${BIND_ADDR:-127.0.0.1}:${APP_PORT:-3000}:3000"
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
migrate:
|
||
condition: service_completed_successfully
|
||
networks:
|
||
- intern
|
||
- extern
|
||
healthcheck:
|
||
# 127.0.0.1, nicht localhost: busybox-wget loest localhost zuerst nach ::1
|
||
# auf, Next lauscht aber nur auf 0.0.0.0:3000. Der Healthcheck schlug
|
||
# deshalb dauerhaft fehl, waehrend die App einwandfrei antwortete -
|
||
# gefunden bei der Abnahme zu #019.
|
||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/api/gesundheit"]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 5
|
||
start_period: 20s
|
||
|
||
networks:
|
||
# Kein Gateway: die Datenbank kann weder heraus noch ist sie von aussen erreichbar.
|
||
intern:
|
||
internal: true
|
||
# Nur fuer web: Reverse Proxy, OIDC zu authentik, LDAP zum AD.
|
||
extern:
|
||
|
||
volumes:
|
||
datenbank:
|