# Production Deployment Guide This guide covers deploying the trivia application to production with a single URL serving both the frontend and backend. ## Architecture Overview In production: - Single Flask server serves both the React SPA and API endpoints - Multi-stage Docker build: React is built and copied into Flask's static folder - All requests go to the same domain (e.g., `https://trivia.torrtle.co`) - Nginx/Caddy reverse proxy handles HTTPS and forwards to Flask - WebSocket connections work seamlessly (same origin) ## Quick Start ### 1. Configure Environment ```bash # Copy and edit production environment file cp .env.production.example .env.production # Edit .env.production with your values: # - Set CORS_ORIGINS to your domain # - Configure OIDC/Authelia settings # - Set SESSION_COOKIE_SECURE=true nano .env.production ``` ### 2. Build and Run ```bash # Build the production image docker build -t trivia-app:latest . # Start all services docker compose -f docker-compose.production.yml up -d # View logs docker compose -f docker-compose.production.yml logs -f backend ``` ### 3. Run Database Migrations ```bash # Initialize database (first time only) docker compose -f docker-compose.production.yml exec backend uv run flask db upgrade ``` ### 4. Configure Reverse Proxy #### Option A: Nginx ```bash # Copy example config sudo cp nginx.conf.example /etc/nginx/sites-available/trivia # Update domain and SSL certificate paths sudo nano /etc/nginx/sites-available/trivia # Enable site sudo ln -s /etc/nginx/sites-available/trivia /etc/nginx/sites-enabled/ # Test configuration sudo nginx -t # Reload nginx sudo systemctl reload nginx ``` #### Option B: Caddy (Recommended - Automatic HTTPS!) Caddy is simpler than Nginx and handles HTTPS automatically with Let's Encrypt. ```bash # Install Caddy (if not already installed) # Ubuntu/Debian: sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update sudo apt install caddy # Copy and edit the example Caddyfile sudo cp Caddyfile.example /etc/caddy/Caddyfile # Update your domain in the Caddyfile sudo nano /etc/caddy/Caddyfile # Validate configuration caddy validate --config /etc/caddy/Caddyfile # Restart Caddy sudo systemctl restart caddy # Check status sudo systemctl status caddy ``` **Simple Caddyfile (minimal):** ```caddy trivia.torrtle.co { reverse_proxy localhost:5001 } ``` **That's it!** Caddy automatically: - Obtains SSL certificate from Let's Encrypt - Handles WebSocket upgrades - Redirects HTTP to HTTPS - Renews certificates automatically See `Caddyfile.example` for advanced configuration with security headers, logging, and optional features. ## Services Once deployed, your application will be available at: - **Main App**: `https://trivia.torrtle.co` - **API**: `https://trivia.torrtle.co/api/*` - **WebSocket**: `wss://trivia.torrtle.co/socket.io` - **Flower (optional)**: `https://trivia.torrtle.co/flower/` (if configured in nginx) ## Maintenance ### View Logs ```bash # All services docker compose -f docker-compose.production.yml logs -f # Specific service docker compose -f docker-compose.production.yml logs -f backend docker compose -f docker-compose.production.yml logs -f celery-worker ``` ### Update Application ```bash # Pull latest code git pull # Rebuild and restart docker compose -f docker-compose.production.yml up -d --build # Run any new migrations docker compose -f docker-compose.production.yml exec backend uv run flask db upgrade ``` ### Backup Database ```bash # Backup database volume docker run --rm \ -v trivia-app_trivia-db:/data \ -v $(pwd)/backups:/backup \ alpine tar czf /backup/trivia-db-$(date +%Y%m%d).tar.gz -C /data . # Backup images docker run --rm \ -v trivia-app_trivia-images:/data \ -v $(pwd)/backups:/backup \ alpine tar czf /backup/trivia-images-$(date +%Y%m%d).tar.gz -C /data . ``` ### Restore Database ```bash # Stop services docker compose -f docker-compose.production.yml down # Restore from backup docker run --rm \ -v trivia-app_trivia-db:/data \ -v $(pwd)/backups:/backup \ alpine sh -c "cd /data && tar xzf /backup/trivia-db-YYYYMMDD.tar.gz" # Start services docker compose -f docker-compose.production.yml up -d ``` ## Scaling ### Run Multiple Workers Edit `docker-compose.production.yml`: ```yaml celery-worker: # ... existing config ... deploy: replicas: 3 # Run 3 worker instances ``` ### Use External Database For production at scale, consider using PostgreSQL instead of SQLite: ```yaml # docker-compose.production.yml services: postgres: image: postgres:16-alpine environment: POSTGRES_DB: trivia POSTGRES_USER: trivia POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - postgres-data:/var/lib/postgresql/data backend: environment: - DATABASE_URI=postgresql://trivia:${DB_PASSWORD}@postgres:5432/trivia ``` ## Monitoring ### Health Checks The application includes a health check endpoint: ```bash curl https://trivia.torrtle.co/api/health ``` ### Celery Flower Access Celery task monitoring at `http://localhost:5555` or configure nginx to expose it at `/flower/`. ## Security Checklist - [ ] Set `SESSION_COOKIE_SECURE=true` in `.env.production` - [ ] Set `CORS_ORIGINS` to your specific domain (not `*`) - [ ] Use strong `OIDC_CLIENT_SECRET` - [ ] Enable HTTPS with valid SSL certificate - [ ] Keep Docker images up to date - [ ] Regular database backups - [ ] Restrict Flower access (don't expose publicly) - [ ] Use firewall to restrict port 5001 to localhost only ## Troubleshooting ### WebSocket Connection Issues Ensure your reverse proxy is configured for WebSocket upgrades: - Nginx: `proxy_set_header Upgrade $http_upgrade;` - Caddy: Handles automatically ### CORS Errors Check `CORS_ORIGINS` in `.env.production` matches your domain exactly (including https://). ### 404 on Frontend Routes Flask's catch-all route should serve `index.html` for all non-API routes. Check that React build files exist in `backend/static/`. ### Database Migration Errors ```bash # Check current migration version docker compose -f docker-compose.production.yml exec backend uv run flask db current # Force to specific version (if needed) docker compose -f docker-compose.production.yml exec backend uv run flask db stamp head ```