# 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} # Anhaenge und Fotos liegen im Dateisystem, nicht in der Datenbank (#032). # Der Pfad zeigt in das Volume `anhaenge` unten. ANHAENGE_PFAD: /daten/anhaenge ANHANG_MAX_MB: ${ANHANG_MAX_MB:-10} ANHAENGE_JE_LAUF: ${ANHAENGE_JE_LAUF:-20} # Standardmaessig nur auf localhost; davor gehoert ein Reverse Proxy. ports: - "${BIND_ADDR:-127.0.0.1}:${APP_PORT:-3000}:3000" volumes: # **Gehoert in jede Sicherung.** Eine Sicherung, die nur `pg_dump` macht, # verliert ab hier Daten: in der Datenbank stehen nur die Metadaten der # Anhaenge, die Dateien liegen hier. Siehe docs/betrieb/sicherung.md. - anhaenge:/daten/anhaenge 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: # Die Dateien der Anhaenge (#032). Zusammen mit der Datenbank zu sichern – # getrennt gesichert ergeben beide Haelften kein vollstaendiges Bild. anhaenge: