From d1208fa8b0b117df85e663cf32b8b10c6494cb86 Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Fri, 12 Sep 2025 14:57:04 +0100 Subject: [PATCH] chore(deploy): prod deploy config --- deploy/.env.example | 5 ++ deploy/docker-compose.yml | 77 +++++++++++++++++++++++++ deploy/nginx.conf | 50 +++++++++++++++++ deploy/readme.md | 114 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 deploy/.env.example create mode 100644 deploy/docker-compose.yml create mode 100644 deploy/nginx.conf create mode 100644 deploy/readme.md diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..c12d38d --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,5 @@ +# Database +POSTGRES_PASSWORD=your_secure_password_here + +# Cloudflare Tunnel token from Zero Trust dashboard +CLOUDFLARE_TUNNEL_TOKEN=your_tunnel_token_here diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..7f2bda1 --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,77 @@ +services: + db: + image: postgres:16-alpine + container_name: codetutor-db + environment: + POSTGRES_USER: codetutor + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} + POSTGRES_DB: codetutor + volumes: + - ./data/postgres:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U codetutor"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - internal + + backend: + build: + context: ../backend + dockerfile: Dockerfile + container_name: codetutor-backend + environment: + DATABASE_URL: postgresql+asyncpg://codetutor:${POSTGRES_PASSWORD}@db:5432/codetutor + CORS_ORIGINS: '["https://codetutor-demo.kschappell.com", "https://kschappell.com"]' + depends_on: + db: + condition: service_healthy + restart: unless-stopped + networks: + - internal + + frontend: + build: + context: ../frontend + dockerfile: Dockerfile + container_name: codetutor-frontend + environment: + NEXT_PUBLIC_API_URL: https://codetutor-demo.kschappell.com + depends_on: + - backend + restart: unless-stopped + networks: + - internal + + nginx: + image: nginx:alpine + container_name: codetutor-nginx + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - frontend + - backend + restart: unless-stopped + networks: + - internal + - public-network + + cloudflared: + image: cloudflare/cloudflared:latest + container_name: codetutor-cloudflared + command: tunnel run + environment: + TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN:?CLOUDFLARE_TUNNEL_TOKEN is required} + depends_on: + - nginx + restart: unless-stopped + networks: + - public-network + +networks: + internal: + driver: bridge + public-network: + external: true diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..91916e8 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,50 @@ +events { + worker_connections 1024; +} + +http { + upstream frontend { + server frontend:3000; + } + + upstream backend { + server backend:8000; + } + + server { + listen 80; + server_name _; + + # Allow iframe embedding from portfolio + add_header Content-Security-Policy "frame-ancestors 'self' https://kschappell.com https://*.kschappell.com" always; + + # API routes to backend + location /api { + proxy_pass http://backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Everything else to frontend + location / { + proxy_pass http://frontend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support for Next.js HMR (dev) and any real-time features + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # Health check endpoint + location /health { + return 200 'OK'; + add_header Content-Type text/plain; + } + } +} diff --git a/deploy/readme.md b/deploy/readme.md new file mode 100644 index 0000000..a9c5680 --- /dev/null +++ b/deploy/readme.md @@ -0,0 +1,114 @@ +# CodeTutor Deployment + +Production deployment configuration using Docker Compose and Cloudflare Tunnel. + +## Architecture + +``` +Internet -> Cloudflare Tunnel -> nginx -> frontend (Next.js) + \-> backend (FastAPI) + \-> PostgreSQL +``` + +## Prerequisites + +- Docker and Docker Compose +- Cloudflare account with Zero Trust access +- External Docker network: `public-network` + +Create the external network if it doesn't exist: + +```bash +docker network create public-network +``` + +## Setup + +### 1. Configure Environment + +```bash +cp .env.example .env +``` + +Edit `.env` with: + +- `POSTGRES_PASSWORD`: Strong password for the database +- `CLOUDFLARE_TUNNEL_TOKEN`: Token from Cloudflare Zero Trust dashboard + +### 2. Create Cloudflare Tunnel + +1. Go to [Cloudflare Zero Trust Dashboard](https://one.dash.cloudflare.com/) +2. Navigate to Networks > Tunnels +3. Create a new tunnel named `codetutor-demo` +4. Add public hostname: + - Subdomain: `codetutor-demo` + - Domain: `kschappell.com` + - Service: `http://nginx:80` +5. Copy the tunnel token to your `.env` file + +### 3. Deploy + +```bash +docker compose up -d --build +``` + +### 4. Verify + +- Check service health: `docker compose ps` +- View logs: `docker compose logs -f` +- Test endpoint: `curl https://codetutor-demo.kschappell.com/health` + +## Database Management + +### Initial Setup + +The database is automatically created on first run. To seed with initial data: + +```bash +docker compose exec backend python -m alembic upgrade head +docker compose exec backend python -m scripts.seed_db +``` + +### Backups + +```bash +docker compose exec db pg_dump -U codetutor codetutor > backup.sql +``` + +### Restore + +```bash +docker compose exec -T db psql -U codetutor codetutor < backup.sql +``` + +## Updating + +```bash +git pull +docker compose up -d --build +``` + +## Troubleshooting + +### Services not starting + +Check logs for specific service: + +```bash +docker compose logs backend +docker compose logs frontend +docker compose logs nginx +``` + +### Database connection issues + +Ensure the database is healthy: + +```bash +docker compose ps db +docker compose logs db +``` + +### Tunnel not connecting + +Verify the tunnel token is correct and the tunnel is active in the Cloudflare dashboard.