# Production Docker-Compose. Deployment auf ToolCTRL-interne Infrastruktur. # Erforderliche Env-Datei: .env.production (per Docker-Secret-Mount oder # Compose-Override). services: postgres: image: postgres:16-alpine container_name: cockpit-postgres restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-cockpit} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB:-cockpit} TZ: Europe/Berlin volumes: - cockpit_postgres_data:/var/lib/postgresql/data - cockpit_backups:/backups healthcheck: test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER:-cockpit} -d $${POSTGRES_DB:-cockpit}'] interval: 10s timeout: 5s retries: 5 # Backup-Sidecar: täglicher pg_dump des postgres-Service nach cockpit_backups. # Nutzt scripts/backup.sh (Custom-Format-Dump + Retention + API-Heartbeat), # getaktet von scripts/backup-loop.sh. Stock postgres-Image → pg_dump passt # versionsgenau (16) zur DB. Restore: siehe docs/operations.md. backup: image: postgres:16-alpine container_name: cockpit-backup restart: unless-stopped depends_on: postgres: condition: service_healthy environment: POSTGRES_USER: ${POSTGRES_USER:-cockpit} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB:-cockpit} PGHOST: postgres API_URL: http://api:3000 CRON_BEARER_TOKEN: ${CRON_BEARER_TOKEN} BACKUP_HOUR: ${BACKUP_HOUR:-03} BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-14} TZ: Europe/Berlin volumes: - cockpit_backups:/backups - ./scripts/backup.sh:/usr/local/bin/backup.sh:ro - ./scripts/backup-loop.sh:/usr/local/bin/backup-loop.sh:ro entrypoint: ['/bin/sh', '/usr/local/bin/backup-loop.sh'] api: build: context: . dockerfile: apps/api/Dockerfile container_name: cockpit-api restart: unless-stopped depends_on: postgres: condition: service_healthy environment: NODE_ENV: production DATABASE_URL: postgres://${POSTGRES_USER:-cockpit}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-cockpit} AUTHENTIK_ISSUER: ${AUTHENTIK_ISSUER} AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID} AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET} AUTHENTIK_REDIRECT_URI: ${AUTHENTIK_REDIRECT_URI} SESSION_COOKIE_SECRET: ${SESSION_COOKIE_SECRET} CRON_BEARER_TOKEN: ${CRON_BEARER_TOKEN} PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-https://cockpit.tool-ctrl.de} LOG_LEVEL: ${LOG_LEVEL:-info} PORT: 3000 healthcheck: test: ['CMD', 'wget', '-qO-', 'http://localhost:3000/api/health'] interval: 15s timeout: 5s retries: 3 web: build: context: . dockerfile: apps/web/Dockerfile container_name: cockpit-web restart: unless-stopped # Web ist statisches Asset-Image; Caddy mountet /srv aus diesem Container. # In Compose teilen wir es via volume statt named-host. volumes: - cockpit_web_dist:/srv caddy: image: caddy:2-alpine container_name: cockpit-caddy restart: unless-stopped depends_on: - api - web ports: - '80:80' - '443:443' environment: COCKPIT_HOST: ${COCKPIT_HOST:-cockpit.tool-ctrl.de} volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - cockpit_web_dist:/srv:ro - cockpit_caddy_data:/data - cockpit_caddy_config:/config volumes: cockpit_postgres_data: cockpit_backups: cockpit_web_dist: cockpit_caddy_data: cockpit_caddy_config: