Skip to content

Deployment Guide

Production deployment guide for Vows Social AI.


Deployment Architecture

Stack: - Cloudflare Workers → API & Orchestrator - Fly.io → Python ML services - Qdrant Cloud → Vector database - Supabase → PostgreSQL - Vercel → Frontend (optional)


Prerequisites

Required Accounts

  • Cloudflare Account (Workers, Durable Objects)
  • Fly.io Account (ML inference)
  • Qdrant Cloud Account (Vector DB)
  • Supabase Account (PostgreSQL)
  • GitHub Account (CI/CD)

Required Tools

# Install wrangler (Cloudflare CLI)
npm install -g wrangler

# Install flyctl (Fly.io CLI)
brew install flyctl  # macOS
# OR
curl -L https://fly.io/install.sh | sh  # Linux

# Install supabase CLI
brew install supabase/tap/supabase  # macOS

# Install gh (GitHub CLI)
brew install gh

Environment Setup

1. Cloudflare Workers

Initialize Project:

cd workers/orchestrator
npm install

Configure wrangler.toml:

name = "vows-orchestrator"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[durable_objects]
bindings = [
  { name = "ORCHESTRATOR", class_name = "OrchestratorDO" }
]

[[migrations]]
tag = "v1"
new_classes = ["OrchestratorDO"]

[vars]
ENVIRONMENT = "production"

[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-namespace-id"

[[r2_buckets]]
binding = "MODELS"
bucket_name = "vows-models"

Set Secrets:

wrangler secret put QDRANT_API_KEY
wrangler secret put QDRANT_URL
wrangler secret put SUPABASE_URL
wrangler secret put SUPABASE_KEY
wrangler secret put OPENAI_API_KEY
wrangler secret put GEMINI_API_KEY

Deploy:

wrangler deploy

2. Fly.io ML Service

Initialize Fly App:

cd services/ml-inference
fly launch --name vows-ml-inference

Configure fly.toml:

app = "vows-ml-inference"
primary_region = "syd"  # Sydney

[build]
  dockerfile = "Dockerfile"

[env]
  PORT = "8080"

[[services]]
  internal_port = 8080
  protocol = "tcp"

  [[services.ports]]
    port = 80
    handlers = ["http"]

  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]

[[services.http_checks]]
  interval = 10000
  timeout = 2000
  path = "/health"

[compute]
  cpu_kind = "shared"
  cpus = 1
  memory_mb = 256

Set Secrets:

fly secrets set QDRANT_API_KEY=xxx
fly secrets set QDRANT_URL=xxx

Deploy:

fly deploy

3. Qdrant Cloud

Create Cluster: 1. Go to https://cloud.qdrant.io 2. Create new cluster (1GB free tier) 3. Note the URL and API key

Initialize Collections:

curl -X PUT 'https://your-cluster.qdrant.io/collections/content_embeddings' \
  -H 'api-key: your-api-key' \
  -H 'Content-Type: application/json' \
  -d '{
    "vectors": {
      "size": 384,
      "distance": "Cosine"
    }
  }'

curl -X PUT 'https://your-cluster.qdrant.io/collections/user_embeddings' \
  -H 'api-key: your-api-key' \
  -H 'Content-Type: application/json' \
  -d '{
    "vectors": {
      "size": 384,
      "distance": "Cosine"
    }
  }'

4. Supabase

Create Project: 1. Go to https://supabase.com 2. Create new project 3. Note the URL and anon key

Run Migrations:

cd migrations
psql $DATABASE_URL < 001_initial_schema.sql
psql $DATABASE_URL < 002_interactions.sql
psql $DATABASE_URL < 003_collections.sql

Or using Supabase CLI:

supabase db push


CI/CD Pipeline

GitHub Actions Workflow

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm test

  deploy-workers:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npx wrangler deploy
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

  deploy-ml-service:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

Deployment Stages

1. Staging (on PR): - Deploy to preview environment - Run integration tests - Manual QA approval

2. Production (on merge to main): - Run full test suite - Deploy to production - Run smoke tests - Monitor metrics


Database Migrations

Migration Files

-- migrations/001_initial_schema.sql
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email TEXT UNIQUE NOT NULL,
  wedding_date DATE,
  location JSONB,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE interactions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id),
  content_id TEXT NOT NULL,
  action TEXT NOT NULL,
  duration FLOAT,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_interactions_user ON interactions(user_id);
CREATE INDEX idx_interactions_content ON interactions(content_id);

Running Migrations

Manually:

psql $DATABASE_URL < migrations/001_initial_schema.sql

With Supabase CLI:

supabase migration new initial_schema
# Edit migration file
supabase db push

Rollback:

supabase db reset


Monitoring & Observability

Cloudflare Analytics

// Track metrics
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const start = Date.now();

    try {
      const response = await handleRequest(request, env);

      // Log metrics
      ctx.waitUntil(
        env.ANALYTICS.writeDataPoint({
          blobs: ['feed-generation'],
          doubles: [Date.now() - start],
          indexes: [request.headers.get('user-agent')]
        })
      );

      return response;
    } catch (error) {
      // Log error
      ctx.waitUntil(logError(error, env));
      throw error;
    }
  }
};

Prometheus Metrics (Fly.io)

from prometheus_client import Counter, Histogram, generate_latest

# Metrics
requests_total = Counter('requests_total', 'Total requests')
request_duration = Histogram('request_duration_seconds', 'Request duration')

@app.get('/metrics')
async def metrics():
    return Response(generate_latest(), media_type='text/plain')

Sentry Error Tracking

import * as Sentry from '@sentry/cloudflare';

Sentry.init({
  dsn: env.SENTRY_DSN,
  environment: env.ENVIRONMENT,
  tracesSampleRate: 0.1
});

export default {
  async fetch(request, env, ctx) {
    try {
      return await handleRequest(request, env);
    } catch (error) {
      Sentry.captureException(error);
      throw error;
    }
  }
};

Deployment Checklist

Pre-Deployment

  • All tests passing
  • Code reviewed and approved
  • Database migrations ready
  • Environment variables configured
  • Secrets set in all services
  • Monitoring dashboards configured
  • Backup strategy in place

Deployment

  • Run database migrations
  • Deploy ML service (Fly.io)
  • Deploy Workers (Cloudflare)
  • Verify deployments successful
  • Run smoke tests
  • Check error rates

Post-Deployment

  • Monitor latency metrics
  • Check error logs
  • Verify user flows working
  • Monitor cost usage
  • Update changelog
  • Notify team

Rollback Procedure

Quick Rollback (Workers)

# List deployments
wrangler deployments list

# Rollback to previous
wrangler rollback --message "Rolling back due to issue"

Rollback ML Service

# List app versions
fly releases

# Rollback to previous
fly releases rollback

Database Rollback

# Restore from backup
supabase db reset --db-url $BACKUP_URL

Health Checks

Workers Health Endpoint

app.get('/health', async (c) => {
  const checks = {
    qdrant: await checkQdrant(c.env),
    supabase: await checkSupabase(c.env),
    mlService: await checkMLService(c.env)
  };

  const healthy = Object.values(checks).every(c => c.status === 'ok');

  return c.json({
    status: healthy ? 'healthy' : 'degraded',
    checks,
    timestamp: new Date().toISOString()
  }, healthy ? 200 : 503);
});

Monitoring Health

# Check Workers
curl https://vows-orchestrator.workers.dev/health

# Check ML Service
curl https://vows-ml-inference.fly.dev/health

Scaling

Cloudflare Workers

  • Automatically scales
  • No configuration needed
  • Pay per request

Fly.io Scaling

# Scale to 3 instances
fly scale count 3

# Scale memory
fly scale memory 512

# Auto-scaling
fly autoscale set min=1 max=10

Qdrant Scaling

  • Upgrade cluster size in dashboard
  • $49/month for 5GB
  • $99/month for 10GB

Supabase Scaling

  • Pro tier: $25/month
  • Increased storage and bandwidth
  • Better performance

Cost Monitoring

Expected Costs (Production)

Free Tier (0-500 users): $0/month

Early Scale (500-2K users): ~$109/month - Cloudflare: $25 - Qdrant: $49 - Supabase: $25 - Fly.io: $10

Growth (2K-10K users): ~$449/month - Cloudflare: $175 - Qdrant: $99 - Supabase: $25 - Fly.io: $50 - AI/ML: $100

Cost Alerts

# Set up billing alerts in each service
# Cloudflare: Dashboard → Billing → Alerts
# Fly.io: fly billing alerts set 100
# Monitor daily in dashboards

Resources