Skip to content

Cloudflare Deployment Guide

This guide covers deploying ZServed to production on Cloudflare Workers, including setup, configuration, and ongoing maintenance.

Prerequisites

Before deploying to production, ensure you have:

  • Cloudflare Account with Workers Paid plan ($5/month minimum)
  • Domain Name registered and managed through Cloudflare
  • Wrangler CLI installed and authenticated
  • Environment Secrets prepared for production
  • Database Backups if migrating from development

Production Environment Setup

Cloudflare Plan Requirements

Workers Plan

  • Paid Plan Required: $5/month minimum for production features
  • Workers Unlimited: 100,000 requests/day on free tier (insufficient for production)
  • Custom Domains: Only available on paid plans
  • KV Storage: Higher limits on paid plans

Additional Services

  • Cloudflare R2: $0.015/GB/month storage
  • Cloudflare D1: Currently in beta, check pricing
  • AutoRAG: Additional charges apply

Domain Configuration

Add Domain to Cloudflare

  1. Add your domain to Cloudflare
  2. Update nameservers at your domain registrar
  3. Verify DNS is working

Create Subdomain Structure

Terminal window
# Set up wildcard DNS for multi-tenant subdomains
# A record: *.yourdomain.com β†’ your-worker.your-subdomain.workers.dev
# Or use Cloudflare's automatic subdomain routing

Production Resource Creation

Database Setup

Terminal window
# Create production database
wrangler d1 create zserved-production
# Run schema migration
wrangler d1 execute zserved-production --file=./migrations/schema.sql
# Verify schema
wrangler d1 execute zserved-production --command="SELECT name FROM sqlite_master WHERE type='table';"

Storage Configuration

Terminal window
# Create production R2 buckets
wrangler r2 bucket create zserved-files-prod
wrangler r2 bucket create zserved-files-preview-prod
# Set up bucket CORS if needed
wrangler r2 bucket cors put zserved-files-prod --file=cors.json

CORS Configuration (cors.json):

[
{
"AllowedOrigins": ["https://yourdomain.com", "https://*.yourdomain.com"],
"AllowedMethods": ["GET", "POST", "PUT", "DELETE"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]

KV Namespace Setup

Terminal window
# Create production KV namespaces
wrangler kv:namespace create "TENANTS" --env production
wrangler kv:namespace create "FILE_METADATA" --env production
# Optional: Migrate data from development
wrangler kv:bulk put TENANTS development_data.json --env production

AI Services Setup

Terminal window
# Create production Vectorize indexes
wrangler vectorize create document-embeddings-prod --preset @cf/baai/bge-base-en-v1.5
wrangler vectorize create client-metadata-prod --preset @cf/baai/bge-base-en-v1.5
# Create production AutoRAG instance
wrangler autorag create zserved-legal-knowledge-prod \
--description "Production legal knowledge base"

Production Configuration

Environment Configuration

Create production environment in wrangler.jsonc:

{
"name": "zserved",
"main": "dist/_worker.js",
"compatibility_date": "2025-04-15",
"env": {
"production": {
"vars": {
"NODE_ENV": "production",
"ENVIRONMENT": "production"
},
"kv_namespaces": [
{
"binding": "TENANTS",
"id": "your_production_tenants_id"
},
{
"binding": "FILE_METADATA",
"id": "your_production_file_metadata_id"
}
],
"r2_buckets": [
{
"binding": "zserved_files",
"bucket_name": "zserved-files-prod"
}
],
"d1_databases": [
{
"binding": "DB",
"database_name": "zserved-production",
"database_id": "your_production_db_id"
}
],
"vectorize": [
{
"binding": "VECTORIZE",
"index_name": "document-embeddings-prod"
},
{
"binding": "CLIENT_VECTORIZE",
"index_name": "client-metadata-prod"
}
],
"autorag": [
{
"binding": "LEGAL_RAG",
"autorag_name": "zserved-legal-knowledge-prod"
}
]
}
}
}

Secrets Management

Set production secrets securely:

Terminal window
# Set API keys and sensitive configuration
wrangler secret put OPENAI_API_KEY --env production
wrangler secret put SQUARE_ACCESS_TOKEN --env production
wrangler secret put SQUARE_APPLICATION_ID --env production
wrangler secret put SQUARE_WEBHOOK_SIGNATURE_KEY --env production
# Database encryption key (if using custom encryption)
wrangler secret put DATABASE_ENCRYPTION_KEY --env production
# JWT signing secret
wrangler secret put JWT_SECRET --env production
# Email service credentials
wrangler secret put SENDGRID_API_KEY --env production

Custom Domain Setup

Terminal window
# Add custom domain to your Worker
wrangler publish --env production
wrangler route put "yourdomain.com/*" zserved --env production
wrangler route put "*.yourdomain.com/*" zserved --env production
# Enable Always Use HTTPS in Cloudflare dashboard
# SSL/TLS β†’ Edge Certificates β†’ Always Use HTTPS: On

Build and Deployment Process

Pre-deployment Checklist

Code Quality

Terminal window
# Run type checking
npm run type-check
# Run linting
npm run lint
# Run tests
npm test
npm run test:integration
# Build frontend
cd webapp
npm run build
cd ..

Security Review

  • Verify no hardcoded secrets in code
  • Check environment variable configuration
  • Review CORS settings
  • Validate authentication flows

Performance Testing

Terminal window
# Test build size
npm run build:analyze
# Run performance tests
npm run test:performance

Deployment Steps

1. Build Application

Terminal window
# Clean previous builds
rm -rf dist webapp/dist
# Build frontend
cd webapp
npm run build
cd ..
# Build worker bundle
npm run build

2. Deploy to Production

Terminal window
# Deploy with production environment
wrangler deploy --env production
# Verify deployment
wrangler tail --env production
# Test endpoints
curl https://yourdomain.com/api/health

3. Database Migration

Terminal window
# Apply any pending migrations
wrangler d1 execute zserved-production --file=./migrations/latest.sql --env production
# Verify data integrity
wrangler d1 execute zserved-production --command="SELECT COUNT(*) FROM tenants;" --env production

Deployment Automation

GitHub Actions Workflow (.github/workflows/deploy.yml):

name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: |
npm install
cd webapp && npm install
- name: Run tests
run: |
npm test
npm run type-check
- name: Build application
run: |
cd webapp && npm run build
cd .. && npm run build
- name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
environment: production
command: deploy --env production

Post-Deployment Configuration

SSL/TLS Configuration

Enable Full (Strict) SSL

  1. Go to Cloudflare Dashboard β†’ SSL/TLS
  2. Set encryption mode to β€œFull (strict)”
  3. Enable β€œAlways Use HTTPS”
  4. Configure minimum TLS version to 1.2

Security Headers

// Add to your Worker response headers
const securityHeaders = {
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin'
};

CDN and Caching

Configure Cache Rules

Terminal window
# Page Rules in Cloudflare Dashboard:
# *.yourdomain.com/api/* β†’ Cache Level: Bypass
# *.yourdomain.com/_astro/* β†’ Cache Level: Cache Everything, TTL: 1 year
# *.yourdomain.com/images/* β†’ Cache Level: Cache Everything, TTL: 1 month

Worker Caching

// Implement intelligent caching in your Worker
const cache = caches.default;
const cacheKey = new Request(url.toString(), request);
// Check cache first for static content
let response = await cache.match(cacheKey);
if (!response) {
response = await handleRequest(request);
// Cache static assets
if (url.pathname.startsWith('/_astro/')) {
response.headers.set('Cache-Control', 'public, max-age=31536000');
await cache.put(cacheKey, response.clone());
}
}

Data Seeding

Seed Production Knowledge Base

Terminal window
# Seed with legal documents
cd scripts
npx tsx seed-knowledge-base.ts production
# Create initial tenant(s)
wrangler d1 execute zserved-production --command="
INSERT INTO tenants (id, name, slug, subdomain, contact_email)
VALUES ('main-tenant', 'Your Law Firm', 'your-firm', 'app', 'admin@yourdomain.com');
" --env production

Monitoring and Maintenance

Performance Monitoring

Cloudflare Analytics

  • Monitor request volume and response times
  • Track error rates and geographic distribution
  • Review cache hit ratios

Worker Analytics

Terminal window
# View Worker metrics
wrangler metrics --env production
# Monitor real-time logs
wrangler tail --env production
# Check resource usage
wrangler kv:key list --binding=TENANTS --env production | wc -l

Custom Monitoring

// Add performance tracking to your Worker
class PerformanceTracker {
static async track(request: Request, response: Response, duration: number) {
// Log to external monitoring service
if (duration > 1000) {
console.warn(`Slow request: ${request.url} took ${duration}ms`);
}
}
}

Health Checks

Implement Health Check Endpoint

// /api/health endpoint
export const GET: APIRoute = async ({ locals }) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
services: {
database: await checkDatabase(locals.runtime.env.DB),
storage: await checkR2(locals.runtime.env.zserved_files),
autorag: await checkAutoRAG(locals.runtime.env.LEGAL_RAG)
}
};
const overallStatus = Object.values(health.services).every(s => s === 'healthy')
? 'healthy' : 'degraded';
return new Response(JSON.stringify({ ...health, status: overallStatus }), {
status: overallStatus === 'healthy' ? 200 : 503,
headers: { 'Content-Type': 'application/json' }
});
};

External Monitoring

Terminal window
# Set up monitoring with services like:
# - UptimeRobot
# - Pingdom
# - DataDog
# - New Relic
# Example health check
curl -f https://yourdomain.com/api/health || exit 1

Backup and Recovery

Database Backups

Terminal window
# Export database backup
wrangler d1 export zserved-production --output=backup-$(date +%Y%m%d).sql --env production
# Schedule regular backups via cron
0 2 * * * cd /path/to/zserved && wrangler d1 export zserved-production --output=backups/backup-$(date +\%Y\%m\%d).sql --env production

File Storage Backups

Terminal window
# Use Cloudflare R2 lifecycle rules for backup retention
# Or sync to external backup service
aws s3 sync s3://zserved-files-prod s3://zserved-backups/$(date +%Y%m%d)/ --exclude "*" --include "*.pdf"

Security Maintenance

Regular Security Updates

Terminal window
# Update dependencies
npm audit
npm update
# Check for security vulnerabilities
npm audit --audit-level high

Access Review

  • Review Cloudflare API tokens quarterly
  • Audit user permissions and roles
  • Monitor authentication logs
  • Review worker analytics for suspicious patterns

Security Monitoring

// Log security events
class SecurityLogger {
static logFailedAuth(request: Request, reason: string) {
console.warn(`Failed auth: ${request.headers.get('CF-Connecting-IP')} - ${reason}`);
}
static logSuspiciousActivity(request: Request, activity: string) {
console.error(`Suspicious activity: ${request.url} - ${activity}`);
}
}

Scaling Considerations

Performance Optimization

Worker Optimization

  • Minimize bundle size
  • Use streaming for large responses
  • Implement smart caching strategies
  • Optimize database queries

Database Scaling

  • Use prepared statements
  • Implement connection pooling
  • Consider read replicas for heavy read workloads
  • Archive old data to reduce database size

Storage Optimization

  • Use appropriate R2 storage classes
  • Implement file compression
  • Set up intelligent tiering
  • Monitor storage costs

Traffic Management

Rate Limiting

// Implement rate limiting per tenant
class RateLimiter {
static async checkLimit(tenantId: string, endpoint: string): Promise<boolean> {
const key = `rate_limit:${tenantId}:${endpoint}`;
const count = await env.KV.get(key);
if (count && parseInt(count) > 100) {
return false; // Rate limited
}
await env.KV.put(key, (parseInt(count || '0') + 1).toString(), { expirationTtl: 3600 });
return true;
}
}

Load Distribution

  • Use Cloudflare’s global network
  • Implement geographic request routing
  • Consider multiple Worker deployments for redundancy

Cost Optimization

Resource Monitoring

Track Usage

Terminal window
# Monitor Workers usage
wrangler metrics --env production
# Check D1 usage
wrangler d1 info zserved-production --env production
# Monitor R2 costs
wrangler r2 bucket info zserved-files-prod

Cost Alerts

  • Set up Cloudflare billing alerts
  • Monitor usage trends
  • Implement usage quotas per tenant

Optimization Strategies

Reduce Costs

  • Optimize worker execution time
  • Use efficient caching strategies
  • Implement data lifecycle policies
  • Monitor and eliminate unused resources

Usage-Based Billing

// Track tenant usage for billing
class UsageTracker {
static async recordUsage(tenantId: string, operation: string, cost: number) {
const key = `usage:${tenantId}:${new Date().toISOString().slice(0, 7)}`;
const current = await env.KV.get(key) || '0';
await env.KV.put(key, (parseFloat(current) + cost).toString());
}
}

This deployment guide ensures your ZServed platform runs reliably and securely in production while maintaining optimal performance and cost efficiency.