# 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: