# Production stack: caddy (auto-HTTPS) -> web (nginx: SPA + /api proxy) -> backend + mysql. # # cp .env.example .env.production # fill the PRODUCTION section first # docker compose --env-file .env.production -f docker-compose.prod.yml up -d --build # # --env-file is REQUIRED: the ${AR_DOMAIN} / ${MYSQL_ROOT_PASSWORD} references below are # resolved from it (env_file: alone only feeds the containers, not this YAML). # # Sized for one 8 GB server (300-500 MB Excel parsing needs the RAM). Backend runs ONE # worker by design — jobs and their progress live in-process. See deploy/DEPLOY.md. services: mysql: image: mysql:8.4 restart: unless-stopped env_file: .env.production # uses MYSQL_PASSWORD / MYSQL_DATABASE / MYSQL_USER environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?set MYSQL_ROOT_PASSWORD in .env.production} command: - --innodb-buffer-pool-size=1G - --max-allowed-packet=256M volumes: - mysql_data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 12 # Not exposed to the host network — only the backend reaches it. backend: build: ./backend restart: unless-stopped env_file: .env.production environment: AR_DATA_DIR: /data MYSQL_HOST: mysql AR_DB_BACKEND: mysql volumes: - ar_data:/data depends_on: mysql: condition: service_healthy healthcheck: test: ["CMD", "python", "-c", "import urllib.request;urllib.request.urlopen('http://localhost:8000/api/health', timeout=5)"] interval: 30s timeout: 10s retries: 3 web: build: context: ./frontend target: prod restart: unless-stopped depends_on: - backend # Not exposed directly — caddy fronts it with TLS. # TLS terminator: automatic Let's Encrypt certificates for AR_DOMAIN, renewed by itself. # No certbot cron, no cert plumbing. Set AR_DOMAIN (and a DNS A record) and it works. caddy: image: caddy:2-alpine restart: unless-stopped ports: - "80:80" - "443:443" environment: AR_DOMAIN: ${AR_DOMAIN:?set AR_DOMAIN in .env.production} command: caddy reverse-proxy --from "https://${AR_DOMAIN}" --to web:80 volumes: - caddy_data:/data - caddy_config:/config volumes: mysql_data: ar_data: caddy_data: caddy_config: