services: webserver: build: context: . dockerfile: nginx/Dockerfile container_name: nginx_webserver volumes: - nginx_logs:/var/log/nginx restart: unless-stopped depends_on: api: condition: service_started networks: - proxy # external — reachable by your reverse proxy / traefik - internal # needs a path to the api container api: build: . container_name: wp_api environment: # Preferred: the API builds its own connection string from these and # encodes the password automatically (no manual URL-encoding needed). POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} POSTGRES_HOST: db # Optional full-URL override (must be URL-encoded if used). DATABASE_URL: ${DATABASE_URL:-} # Signs login session cookies. REQUIRED — compose fails fast if it's unset, # and the API refuses to start in production without it (see server/auth.py). AUTH_SECRET_KEY: ${AUTH_SECRET_KEY:?set AUTH_SECRET_KEY in .env (see server/.env.example)} AUTH_SESSION_HOURS: ${AUTH_SESSION_HOURS:-12} # Optional — SMTP password for WP-assignment emails. Email is off by # default and enabled from the Admin console; this is the only email # secret and it is never stored in the DB. Leave unset until configured. SMTP_PASSWORD: ${SMTP_PASSWORD:-} restart: unless-stopped depends_on: db: condition: service_healthy # waits for postgres to accept connections networks: - internal db: image: postgres:16-alpine container_name: wp_db environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - pgdata:/var/lib/postgresql/data restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 10s timeout: 5s retries: 5 networks: - internal # Scheduled pg_dump backups. Writes gzipped, timestamped dumps to ./backups on # the host (sync that folder offsite from the host — this container has no # internet egress). See scripts/db-backup.sh and DEPLOYMENT.md § Backups. backup: build: context: . dockerfile: scripts/backup.Dockerfile # postgres client + openssl container_name: wp_db_backup environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} PGHOST: db BACKUP_DIR: /backups BACKUP_KEEP: ${BACKUP_KEEP:-14} # keep the newest N dumps BACKUP_INTERVAL_SECONDS: ${BACKUP_INTERVAL_SECONDS:-86400} # 86400 = daily # Set BACKUP_ENC_PASSPHRASE in .env to encrypt dumps at rest (AES-256). # Required once the DB holds customer IP. Keep the passphrase off this host. BACKUP_ENC_PASSPHRASE: ${BACKUP_ENC_PASSPHRASE:-} volumes: - ./scripts:/scripts:ro - ./backups:/backups entrypoint: ["/bin/sh", "/scripts/backup-cron.sh"] restart: unless-stopped depends_on: db: condition: service_healthy networks: - internal volumes: pgdata: nginx_logs: networks: proxy: name: proxy external: true internal: internal: true # no outbound internet access from api/db