productionize
This commit is contained in:
232
README.production.md
Normal file
232
README.production.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user