Files
triviathang/README.production.md
2026-01-12 21:24:19 -05:00

6.5 KiB

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

# 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

# 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

# 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

# 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

Caddy is simpler than Nginx and handles HTTPS automatically with Let's Encrypt.

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

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

# 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

# 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

# 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

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

celery-worker:
  # ... existing config ...
  deploy:
    replicas: 3  # Run 3 worker instances

Use External Database

For production at scale, consider using PostgreSQL instead of SQLite:

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

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

# 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