Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Auf einer frischen Datenbank lief nur `migrate deploy`. Die Tabellen standen damit, aber leer: keine Freigabestufen, keine Status-Etiketten, keine Fallarten, keine Nummernkreise. Die Anwendung startet so zwar, aber es laesst sich keine Akte anlegen - es gibt keine Vorlage. Der Seed besteht ausschliesslich aus upserts und darf deshalb bei jedem Start mitlaufen. Dafuer liegen jetzt tsx, der Prisma-Client und der Quelltext des db-Pakets mit im Migrator-Verzeichnis des Images, statt beim Start aus dem Netz geholt zu werden.
245 lines
7.6 KiB
YAML
245 lines
7.6 KiB
YAML
# AfiS-Aktensystem - ein einziger Dockge-Stack.
|
|
# Ablage: /opt/stacks/afis/compose.yaml, .env im eigenen Dockge-Reiter.
|
|
# Begründung der Entscheidungen in docs/11-betrieb.md:
|
|
# - keine Einmal- und keine Init-Container, Dockge zeigt beendete Container nicht
|
|
# - Migration und Seed laufen im Startkommando von `web`
|
|
# - jeder Dienst hat einen Healthcheck, das ist in Dockge die Statusanzeige
|
|
# - kein Reverse Proxy im Stack: TLS macht ein Proxy davor
|
|
#
|
|
# Die drei eigenen Images werden hier gebaut, nicht aus einer Registry gezogen:
|
|
# docker compose build && docker compose up -d
|
|
# Der Baukontext ist das Repository-Wurzelverzeichnis, der Stack-Ordner muss
|
|
# also den Quelltext enthalten.
|
|
#
|
|
# HINWEIS ZU DEN PORTS: `PORT` und die Healthchecks bleiben bei 3000 - das ist
|
|
# der Port *im* Container. Auf welchem Port die Anwendung auf dem Host
|
|
# erreichbar ist, steuert WEB_PORT in der .env.
|
|
|
|
x-logging: &logging
|
|
logging:
|
|
driver: json-file
|
|
options: { max-size: '10m', max-file: '3' }
|
|
|
|
services:
|
|
web:
|
|
<<: *logging
|
|
image: afis-web:local
|
|
build:
|
|
context: .
|
|
dockerfile: docker/web.Dockerfile
|
|
# Nicht erst in einer Registry nachfragen: das Image entsteht hier.
|
|
# Ohne das versucht `up` einen Pull und warnt "pull access denied".
|
|
pull_policy: never
|
|
restart: unless-stopped
|
|
# Die Migration läuft im Startkommando, nicht in einem Init-Container.
|
|
# Die Prisma-CLI liegt im Image unter /app/migrator, es wird nichts nachgeladen.
|
|
command: >
|
|
sh -c "cd /app/migrator
|
|
&& node node_modules/prisma/build/index.js migrate deploy
|
|
&& node node_modules/tsx/dist/cli.mjs prisma/seed.ts
|
|
&& cd /app && node web/server.js"
|
|
env_file: .env
|
|
environment:
|
|
NODE_ENV: production
|
|
PORT: '3000'
|
|
HOSTNAME: '0.0.0.0'
|
|
volumes:
|
|
- files:/data/files
|
|
- clamav_socket:/var/run/clamav
|
|
# Links der Port auf dem Host, rechts der im Container. Nur der linke ist
|
|
# frei waehlbar - ueber WEB_PORT in der .env, damit fuer eine andere
|
|
# Portwahl keine verfolgte Datei angefasst werden muss.
|
|
ports:
|
|
- '${WEB_PORT:-3000}:3000'
|
|
depends_on:
|
|
pgbouncer: { condition: service_healthy }
|
|
clamav: { condition: service_healthy }
|
|
healthcheck:
|
|
test: ['CMD', 'wget', '-qO-', 'http://localhost:3000/api/health']
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 40s
|
|
|
|
worker:
|
|
<<: *logging
|
|
image: afis-worker:local
|
|
build:
|
|
context: .
|
|
dockerfile: docker/worker.Dockerfile
|
|
# Nicht erst in einer Registry nachfragen: das Image entsteht hier.
|
|
# Ohne das versucht `up` einen Pull und warnt "pull access denied".
|
|
pull_policy: never
|
|
restart: unless-stopped
|
|
env_file: .env
|
|
depends_on:
|
|
pgbouncer: { condition: service_healthy }
|
|
healthcheck:
|
|
test: ['CMD', 'node', 'healthcheck.js'] # prüft Gateway-Verbindung
|
|
interval: 60s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
db:
|
|
<<: *logging
|
|
image: postgres:16-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: afis
|
|
POSTGRES_USER: afis
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
TZ: ${TZ}
|
|
volumes:
|
|
- pgdata:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'pg_isready -U afis -d afis']
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
pgbouncer:
|
|
<<: *logging
|
|
image: edoburu/pgbouncer:latest
|
|
restart: unless-stopped
|
|
environment:
|
|
DB_HOST: db
|
|
DB_USER: afis
|
|
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
|
DB_NAME: afis
|
|
POOL_MODE: transaction
|
|
MAX_CLIENT_CONN: '100'
|
|
DEFAULT_POOL_SIZE: '20'
|
|
AUTH_TYPE: scram-sha-256
|
|
# Ausdruecklich 6432: neuere Fassungen des Images lauschen sonst auf
|
|
# 5432, und dann zeigen sowohl DATABASE_URL als auch der Healthcheck
|
|
# ins Leere. 6432 haelt ausserdem sichtbar auseinander, ob eine
|
|
# Verbindung ueber den Pool geht oder an ihm vorbei.
|
|
LISTEN_PORT: '6432'
|
|
depends_on:
|
|
db: { condition: service_healthy }
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'pg_isready -h 127.0.0.1 -p 6432 -U afis']
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
clamav:
|
|
<<: *logging
|
|
image: clamav/clamav:stable
|
|
restart: unless-stopped
|
|
environment:
|
|
CLAMAV_NO_MILTERD: 'true'
|
|
FRESHCLAM_CHECKS: '2'
|
|
volumes:
|
|
- clamav_db:/var/lib/clamav
|
|
- clamav_socket:/var/run/clamav
|
|
- files:/data/files
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'clamdscan --ping 1 || exit 1']
|
|
interval: 60s
|
|
timeout: 15s
|
|
retries: 3
|
|
start_period: 300s # erster Signaturdownload dauert
|
|
|
|
pdf:
|
|
<<: *logging
|
|
image: afis-pdf:local
|
|
build:
|
|
context: .
|
|
dockerfile: docker/pdf.Dockerfile
|
|
# Nicht erst in einer Registry nachfragen: das Image entsteht hier.
|
|
# Ohne das versucht `up` einen Pull und warnt "pull access denied".
|
|
pull_policy: never
|
|
restart: unless-stopped
|
|
environment:
|
|
PORT: '3000'
|
|
APP_URL: ${APP_URL}
|
|
healthcheck:
|
|
test: ['CMD', 'wget', '-qO-', 'http://localhost:3000/health']
|
|
interval: 60s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
# Ein Weg von aussen, falls kein eigener Reverse Proxy vorhanden ist.
|
|
# Startet nur auf ausdrueckliche Anforderung:
|
|
# docker compose --profile cloudflare up -d
|
|
# Wer einen Proxy im eigenen Netz betreibt - Nginx Proxy Manager, Caddy,
|
|
# Traefik -, laesst diesen Dienst weg und leitet dort auf den Port aus
|
|
# `ports` beim Dienst `web`.
|
|
cloudflared:
|
|
<<: *logging
|
|
profiles: ['cloudflare']
|
|
image: cloudflare/cloudflared:latest
|
|
restart: unless-stopped
|
|
command: tunnel --no-autoupdate run
|
|
environment:
|
|
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN}
|
|
depends_on:
|
|
web: { condition: service_healthy }
|
|
healthcheck:
|
|
test: ['CMD', 'cloudflared', '--version']
|
|
interval: 60s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 20s
|
|
|
|
db-backup:
|
|
<<: *logging
|
|
image: prodrigestivill/postgres-backup-local:16
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_HOST: db
|
|
POSTGRES_DB: afis
|
|
POSTGRES_USER: afis
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
POSTGRES_EXTRA_OPTS: '-Fc'
|
|
SCHEDULE: '0 15 4 * * *' # täglich 04:15
|
|
BACKUP_KEEP_DAYS: '7'
|
|
BACKUP_KEEP_WEEKS: '4'
|
|
BACKUP_KEEP_MONTHS: '6'
|
|
HEALTHCHECK_PORT: '8080'
|
|
TZ: ${TZ}
|
|
volumes:
|
|
- /mnt/backup/afis/db:/backups
|
|
depends_on:
|
|
db: { condition: service_healthy }
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'wget -qO- http://localhost:8080/ || exit 1']
|
|
interval: 60s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
offsite-backup:
|
|
<<: *logging
|
|
image: mazzolino/restic:latest
|
|
restart: unless-stopped
|
|
environment:
|
|
RUN_ON_STARTUP: 'false'
|
|
BACKUP_CRON: '0 30 4 * * *' # 15 Minuten nach dem Dump
|
|
RESTIC_REPOSITORY: ${RESTIC_REPOSITORY}
|
|
RESTIC_PASSWORD: ${RESTIC_PASSWORD}
|
|
RESTIC_BACKUP_SOURCES: /data/db /data/files
|
|
RESTIC_FORGET_ARGS: '--keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune'
|
|
TZ: ${TZ}
|
|
volumes:
|
|
- /mnt/backup/afis/db:/data/db:ro
|
|
- files:/data/files:ro
|
|
# Kein HTTP-Endpunkt im Image. Geprüft wird, dass der Cron-Dauerläufer noch
|
|
# steht und restic aufrufbar ist. Ohne Healthcheck wäre der Dienst in Dockge
|
|
# nur "läuft", auch wenn er seit Wochen nichts mehr sichert.
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'pgrep -f go-crond >/dev/null && restic version >/dev/null || exit 1']
|
|
interval: 300s
|
|
timeout: 15s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
volumes:
|
|
pgdata:
|
|
files:
|
|
clamav_db:
|
|
clamav_socket:
|