Files
triviathang/README.production.md
2026-01-12 21:37:39 -05:00

265 lines
6.6 KiB
Markdown

# 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 --app backend.app:create_app 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 --app backend.app:create_app 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 --app backend.app:create_app db current
# Force to specific version (if needed)
docker compose -f docker-compose.production.yml exec backend uv run flask --app backend.app:create_app db stamp head
```