Files
AfiS-Aktensystem/compose.yaml
T
Harlekin e0b03ea336
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
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

201 lines
5.7 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
# - Migrationen laufen im Startkommando von `web`
# - jeder Dienst hat einen Healthcheck, das ist in Dockge die Statusanzeige
# - kein Reverse Proxy: TLS und Kompression macht der Cloudflare Tunnel
#
# DEIN-USER durch den eigenen GitHub-Namen ersetzen.
x-logging: &logging
logging:
driver: json-file
options: { max-size: '10m', max-file: '3' }
services:
web:
<<: *logging
image: ghcr.io/DEIN-USER/afis-web:latest
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 && 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
ports:
- '3000:3000' # nur im LAN, von außen kommt der Tunnel
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: ghcr.io/DEIN-USER/afis-worker:latest
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
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: ghcr.io/DEIN-USER/afis-pdf:latest # Playwright-Basis
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
cloudflared:
<<: *logging
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: