233 lines
5.4 KiB
Markdown
233 lines
5.4 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 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 (Simpler)
|
|
|
|
Create `/etc/caddy/Caddyfile`:
|
|
|
|
```caddy
|
|
trivia.torrtle.co {
|
|
reverse_proxy localhost:5001
|
|
}
|
|
```
|
|
|
|
Caddy automatically handles HTTPS with Let's Encrypt!
|
|
|
|
## 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
|
|
```
|