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
- Add your domain to Cloudflare
- Update nameservers at your domain registrar
- Verify DNS is working
Create Subdomain Structure
# 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
# Create production databasewrangler d1 create zserved-production
# Run schema migrationwrangler d1 execute zserved-production --file=./migrations/schema.sql
# Verify schemawrangler d1 execute zserved-production --command="SELECT name FROM sqlite_master WHERE type='table';"
Storage Configuration
# Create production R2 bucketswrangler r2 bucket create zserved-files-prodwrangler r2 bucket create zserved-files-preview-prod
# Set up bucket CORS if neededwrangler 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
# Create production KV namespaceswrangler kv:namespace create "TENANTS" --env productionwrangler kv:namespace create "FILE_METADATA" --env production
# Optional: Migrate data from developmentwrangler kv:bulk put TENANTS development_data.json --env production
AI Services Setup
# Create production Vectorize indexeswrangler vectorize create document-embeddings-prod --preset @cf/baai/bge-base-en-v1.5wrangler vectorize create client-metadata-prod --preset @cf/baai/bge-base-en-v1.5
# Create production AutoRAG instancewrangler 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:
# Set API keys and sensitive configurationwrangler secret put OPENAI_API_KEY --env productionwrangler secret put SQUARE_ACCESS_TOKEN --env productionwrangler secret put SQUARE_APPLICATION_ID --env productionwrangler 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 secretwrangler secret put JWT_SECRET --env production
# Email service credentialswrangler secret put SENDGRID_API_KEY --env production
Custom Domain Setup
# Add custom domain to your Workerwrangler publish --env productionwrangler route put "yourdomain.com/*" zserved --env productionwrangler 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
# Run type checkingnpm run type-check
# Run lintingnpm run lint
# Run testsnpm testnpm run test:integration
# Build frontendcd webappnpm run buildcd ..
Security Review
- Verify no hardcoded secrets in code
- Check environment variable configuration
- Review CORS settings
- Validate authentication flows
Performance Testing
# Test build sizenpm run build:analyze
# Run performance testsnpm run test:performance
Deployment Steps
1. Build Application
# Clean previous buildsrm -rf dist webapp/dist
# Build frontendcd webappnpm run buildcd ..
# Build worker bundlenpm run build
2. Deploy to Production
# Deploy with production environmentwrangler deploy --env production
# Verify deploymentwrangler tail --env production
# Test endpointscurl https://yourdomain.com/api/health
3. Database Migration
# Apply any pending migrationswrangler d1 execute zserved-production --file=./migrations/latest.sql --env production
# Verify data integritywrangler 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
- Go to Cloudflare Dashboard β SSL/TLS
- Set encryption mode to βFull (strict)β
- Enable βAlways Use HTTPSβ
- Configure minimum TLS version to 1.2
Security Headers
// Add to your Worker response headersconst 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
# 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 Workerconst cache = caches.default;const cacheKey = new Request(url.toString(), request);
// Check cache first for static contentlet 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
# Seed with legal documentscd scriptsnpx 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
# View Worker metricswrangler metrics --env production
# Monitor real-time logswrangler tail --env production
# Check resource usagewrangler kv:key list --binding=TENANTS --env production | wc -l
Custom Monitoring
// Add performance tracking to your Workerclass 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 endpointexport 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
# Set up monitoring with services like:# - UptimeRobot# - Pingdom# - DataDog# - New Relic
# Example health checkcurl -f https://yourdomain.com/api/health || exit 1
Backup and Recovery
Database Backups
# Export database backupwrangler d1 export zserved-production --output=backup-$(date +%Y%m%d).sql --env production
# Schedule regular backups via cron0 2 * * * cd /path/to/zserved && wrangler d1 export zserved-production --output=backups/backup-$(date +\%Y\%m\%d).sql --env production
File Storage Backups
# Use Cloudflare R2 lifecycle rules for backup retention# Or sync to external backup serviceaws s3 sync s3://zserved-files-prod s3://zserved-backups/$(date +%Y%m%d)/ --exclude "*" --include "*.pdf"
Security Maintenance
Regular Security Updates
# Update dependenciesnpm auditnpm update
# Check for security vulnerabilitiesnpm 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 eventsclass 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 tenantclass 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
# Monitor Workers usagewrangler metrics --env production
# Check D1 usagewrangler d1 info zserved-production --env production
# Monitor R2 costswrangler 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 billingclass 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.