Shortlist / DEPLOY.md
Eren-Sama
Initial commit β€” full-stack AI portfolio architect
53e1531

Shortlist β€” Production Deployment Guide

This guide covers deploying Shortlist to a production environment.


Prerequisites

Requirement Version
Docker + Docker Compose 24.x+ / 2.x+
Supabase project Active with tables migrated
Groq API key Active account
Domain + SSL certificate For HTTPS (optional for staging)

1. Environment Setup

Backend Environment

cd backend
cp .env.example .env

Fill in production values:

ENVIRONMENT=production
SECRET_KEY=<generate with: python -c "import secrets; print(secrets.token_urlsafe(64))">
LOG_LEVEL=WARNING
ALLOWED_ORIGINS=https://yourdomain.com
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=<your-anon-key>
SUPABASE_SERVICE_KEY=<your-service-key>
SUPABASE_JWT_SECRET=<your-jwt-secret>
GROQ_API_KEY=<your-groq-key>

CRITICAL: SECRET_KEY must be explicitly set and β‰₯ 32 characters in production. The app will refuse to start without it.

Frontend Environment

cd frontend
cp .env.example .env.local
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your-anon-key>

2. Database Migrations

Apply all migrations to your Supabase project:

cd backend
python apply_migration.py

Or apply individually via the Supabase SQL editor:

migrations/001_initial_schema.sql    β€” Core tables (jd_analyses, capstone_projects, repo_analyses)
migrations/002_scaffolds.sql         β€” Scaffold generator table
migrations/003_portfolio_outputs.sql β€” Portfolio optimizer table

All tables have:

  • UUID primary keys
  • user_id foreign key to auth.users
  • Row Level Security (RLS) enabled
  • Service role bypass policies
  • updated_at auto-trigger

3. Docker Deployment

Build and Start

# Production deployment (from project root)
docker-compose -f docker-compose.prod.yml up --build -d

# Or using Make
make deploy

Verify

# Health check
curl http://localhost:8000/health

# Deep health check (DB + LLM status)
curl http://localhost:8000/health/deep

# Application metrics
curl http://localhost:8000/metrics

# Frontend
curl http://localhost:3000

Logs

docker-compose -f docker-compose.prod.yml logs -f backend
docker-compose -f docker-compose.prod.yml logs -f frontend

4. Architecture

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Nginx / Caddy   β”‚
                    β”‚  (TLS + Proxy)   β”‚
                    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
                         β”‚        β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”  β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚   Backend   β”‚  β”‚   Frontend   β”‚
              β”‚  (Gunicorn  β”‚  β”‚  (Node.js    β”‚
              β”‚   +Uvicorn) β”‚  β”‚   Standalone)β”‚
              β”‚  Port 8000  β”‚  β”‚  Port 3000   β”‚
              β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚          β”‚          β”‚
    β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β–Όβ”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”
    β”‚ Supabase  β”‚ β”‚ Groq β”‚ β”‚ GitHub β”‚
    β”‚ (DB+Auth) β”‚ β”‚ (LLM)β”‚ β”‚  API   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Backend Container

  • Image: python:3.12-slim (multi-stage, ~150MB)
  • Server: Uvicorn with 4 workers (default)
  • Resources: 2 CPU / 2GB RAM limit
  • Health check: GET /health every 30s

Frontend Container

  • Image: node:20-alpine (standalone, ~100MB)
  • Server: Next.js standalone Node.js server
  • Resources: 1 CPU / 512MB RAM limit

5. Monitoring

Endpoints

Endpoint Purpose Auth
GET /health Lightweight LB health check Public
GET /health/deep DB + LLM connectivity check Public (protect in prod)
GET /metrics Request counts, latencies, error rates Public (protect in prod)

Request Tracing

Every request gets an X-Request-ID header:

  • If your reverse proxy sends one, we'll use it
  • Otherwise, a UUID4 is generated
  • Response includes the same X-Request-ID for correlation
  • Slow requests (> 5s) are logged with the request ID

Logged Metrics

  • Request count per endpoint
  • Latency percentiles (p50, p95, p99)
  • HTTP status code distribution
  • Pipeline execution counts and error rates

Recommended Monitoring Stack

Tool Purpose
Sentry Error tracking + alerting
Grafana + Prometheus Metrics dashboards
Datadog / CloudWatch Log aggregation (JSON structured logs)
Uptime Robot External uptime monitoring

6. Security Checklist

  • All API endpoints require JWT authentication
  • IDOR protection on all update operations
  • Error messages sanitized (no internal details leaked)
  • RLS enabled on all tables
  • CORS restricted to explicit origins
  • Rate limiting (60 req/min default)
  • Request size limiting (10MB)
  • Security headers (CSP, HSTS, X-Frame-Options)
  • Non-root container user
  • SECRET_KEY required in production
  • Docs/OpenAPI disabled in production
  • Frontend middleware for route protection
  • Input sanitization on all request schemas

7. Scaling

Horizontal Scaling

# Scale backend workers
docker-compose -f docker-compose.prod.yml up --scale backend=3 -d

Performance Tuning

Config Default Description
WEB_CONCURRENCY 4 Gunicorn workers
RATE_LIMIT_PER_MINUTE 60 Per-IP rate limit
MAX_REQUEST_SIZE_MB 10 Max request body
REPO_CLONE_TIMEOUT_SECONDS 120 Git clone timeout
LLM_MAX_TOKENS 4096 Max LLM response tokens

Future Considerations

  • Redis: For rate limiting, LLM response caching, session storage
  • Celery/ARQ: Background job queue for long-running analyses
  • CDN: CloudFront/Cloudflare for frontend static assets
  • Read Replicas: Supabase connection pooling for high read loads

8. Rollback

# Stop current deployment
docker-compose -f docker-compose.prod.yml down

# Roll back to previous images
docker tag shortlist-backend:previous shortlist-backend:latest
docker tag shortlist-frontend:previous shortlist-frontend:latest

# Restart
docker-compose -f docker-compose.prod.yml up -d

9. Troubleshooting

Issue Solution
Backend won't start Check SECRET_KEY is set in .env
DB connection fails Verify SUPABASE_URL and SUPABASE_SERVICE_KEY
LLM calls failing Check GROQ_API_KEY is valid
Frontend can't reach backend Check NEXT_PUBLIC_API_URL and CORS origins
Docker build fails Check package-lock.json exists for frontend
Slow responses Check /metrics for p95 latencies, scale workers