Deployment
This guide covers deploying your application to Cloudflare Workers with Neon PostgreSQL for production.
Prerequisites
Before deploying, ensure you have:
- Cloudflare Account with Workers enabled
- Neon Account for PostgreSQL hosting
- GitHub Account for CI/CD (optional but recommended)
- Domain Name configured in Cloudflare (optional)
Overview
The deployment architecture consists of:
- Edge Layer: Cloudflare Workers serving both app and API
- Database: Neon PostgreSQL with Hyperdrive connection pooling
- Static Assets: Served directly from Workers with caching
- Environments: Development, Preview, Staging, and Production
Database Setup
1. Create Neon Database
- Sign up at neon.tech
- Create a new project for your application
- Note your connection string (looks like
postgresql://user:pass@host/dbname
)
2. Install PostgreSQL Extensions
Connect to your Neon database and run:
CREATE EXTENSION IF NOT EXISTS "pg_uuidv7";
This extension is required for UUIDv7 primary key generation used throughout the schema.
3. Configure Hyperdrive
Hyperdrive provides connection pooling and caching at the edge:
# Create Hyperdrive configuration for production
wrangler hyperdrive create my-app-prod \
--connection-string="postgresql://user:pass@host/dbname"
# Create separate configuration for staging
wrangler hyperdrive create my-app-staging \
--connection-string="postgresql://user:pass@host/dbname-staging"
Note the Hyperdrive IDs returned - you'll need these for configuration.
4. Run Database Migrations
# Set your database URL
export DATABASE_URL="postgresql://user:pass@host/dbname"
# Generate and apply migrations
bun --filter @repo/db generate
bun --filter @repo/db migrate
Cloudflare Workers Setup
1. Configure Wrangler
Update apps/edge/wrangler.jsonc
with your settings:
{
"name": "your-app-name",
"env": {
"production": {
"routes": [
{
"pattern": "yourdomain.com",
"custom_domain": true,
},
],
"vars": {
"ENVIRONMENT": "production",
"ALLOWED_ORIGINS": "https://yourdomain.com",
},
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "your-hyperdrive-cached-id",
},
{
"binding": "HYPERDRIVE_DIRECT",
"id": "your-hyperdrive-direct-id",
},
],
},
},
}
2. Set Environment Secrets
# Login to Cloudflare
wrangler login
# Set production secrets
wrangler secret put AUTH_SECRET --env production
wrangler secret put GOOGLE_CLIENT_ID --env production
wrangler secret put GOOGLE_CLIENT_SECRET --env production
wrangler secret put GITHUB_CLIENT_ID --env production
wrangler secret put GITHUB_CLIENT_SECRET --env production
Generate AUTH_SECRET
with:
openssl rand -hex 32
3. Build and Deploy
# Build all applications
bun build
# Deploy edge app to production
bun wrangler deploy --config apps/edge/wrangler.jsonc --env=production
# Deploy web app (marketing site) to production
bun wrangler deploy --config apps/web/wrangler.jsonc --env=production
# Or deploy to staging first
bun wrangler deploy --config apps/edge/wrangler.jsonc --env=staging
bun wrangler deploy --config apps/web/wrangler.jsonc --env=staging
OAuth Provider Setup
Google OAuth
- Go to Google Cloud Console
- Create a new project or select existing
- Enable Google+ API
- Create OAuth 2.0 credentials
- Add authorized redirect URIs:
https://yourdomain.com/api/auth/callback/google
https://staging.yourdomain.com/api/auth/callback/google
GitHub OAuth
- Go to GitHub Settings > Developer settings > OAuth Apps
- Create a new OAuth App
- Set Authorization callback URL:
https://yourdomain.com/api/auth/callback/github
Custom Domain Configuration
1. Add Domain to Cloudflare
- Add your domain to Cloudflare DNS
- Update nameservers with your registrar
- Wait for DNS propagation
2. Configure Workers Routes
Routes are already configured in wrangler.jsonc
. The deployment will automatically set them up.
3. SSL/TLS Settings
In Cloudflare Dashboard:
- Go to SSL/TLS > Overview
- Set encryption mode to "Full (strict)"
- Enable "Always Use HTTPS"
CI/CD with GitHub Actions
Basic Deployment Workflow
Create .github/workflows/deploy.yml
:
name: Deploy
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build
run: bun build
- name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --config apps/edge/wrangler.jsonc --env=production
- name: Deploy Web App to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --config apps/web/wrangler.jsonc --env=production
Preview Deployments
For pull request previews:
name: Preview
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build
run: bun build
- name: Deploy Preview
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --config apps/edge/wrangler.jsonc --env=preview
- name: Deploy Web App Preview
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --config apps/web/wrangler.jsonc --env=preview
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '🚀 Preview deployed to https://preview.yourdomain.com'
})
Environment Management
Development
- Local development with
bun dev
- Uses local environment variables from
.env.local
- Connects to development database
Preview
- Deployed on pull requests
- Isolated environment for testing
- Separate database and secrets
Staging
- Pre-production environment
- Production-like configuration
- Final testing before production
Production
- Live environment
- Full monitoring and logging
- Regular backups
Monitoring and Logging
Cloudflare Analytics
Monitor your application in the Cloudflare Dashboard:
- Workers > Analytics
- View requests, errors, and performance metrics
- Set up alerts for anomalies
Wrangler Tail
Stream live logs from your Workers:
# Tail production logs
wrangler tail --env=production
# Filter specific paths
wrangler tail --env=production --search-str="/api/"
Error Tracking
Consider integrating error tracking services:
- Sentry for error monitoring
- Datadog for APM
- LogDNA for log aggregation
Performance Optimization
Edge Caching
Configure cache headers in your API responses:
return new Response(data, {
headers: {
"Cache-Control": "public, max-age=3600",
"CDN-Cache-Control": "max-age=7200",
},
});
Static Asset Optimization
- Enable Cloudflare Auto Minify
- Use Cloudflare Polish for images
- Configure Page Rules for static assets
Database Performance
- Use Hyperdrive for connection pooling
- Implement query result caching
- Add appropriate database indexes
- Monitor slow queries in Neon dashboard
Rollback Strategy
Quick Rollback
# List recent deployments
wrangler deployments list --env=production
# Rollback to specific version
wrangler rollback --env=production --message="Reverting to stable version"
Database Rollback
Keep migration rollback scripts:
# Rollback last migration
bun --filter @repo/db rollback
Security Checklist
Before going to production:
- [ ] All secrets are set via
wrangler secret
- [ ] Authentication is properly configured
- [ ] CORS settings restrict to your domain
- [ ] Rate limiting is implemented
- [ ] Input validation on all endpoints
- [ ] SQL injection protection (Drizzle handles this)
- [ ] XSS protection headers configured
- [ ] HTTPS enforced on all routes
- [ ] Sensitive data is never logged
- [ ] Regular security updates applied
Troubleshooting
Common Issues
Workers Size Limit
If you hit the 10MB Workers size limit:
- Optimize bundle size with tree shaking
- Move large assets to R2 storage
- Split into multiple Workers if needed
Database Connection Issues
- Verify Hyperdrive configuration
- Check connection string format
- Ensure PostgreSQL extensions are installed
- Review Neon connection limits
Authentication Problems
- Verify OAuth redirect URIs
- Check AUTH_SECRET is set correctly
- Ensure cookies are configured for your domain
- Review CORS settings
Debug Mode
Enable verbose logging:
// In apps/edge/index.ts
const DEBUG = env.ENVIRONMENT === "staging";
if (DEBUG) {
console.log("Request:", request.url);
console.log("Headers:", Object.fromEntries(request.headers));
}
Cost Optimization
Cloudflare Workers
- Free tier: 100,000 requests/day
- Paid: $5/month for 10M requests
- Monitor usage in dashboard
Neon PostgreSQL
- Free tier: 0.5 GB storage
- Scale as needed with compute auto-suspend
- Use connection pooling to reduce costs
Optimization Tips
- Implement aggressive caching
- Use KV storage for session data
- Optimize database queries
- Enable Cloudflare's free optimizations
Next Steps
After deployment:
- Set up monitoring - Configure alerts for errors and performance
- Implement backups - Regular database backups with Neon
- Configure CI/CD - Automate deployments with GitHub Actions
- Add custom domain - Configure your domain in Cloudflare
- Enable analytics - Track user behavior and performance
- Security audit - Regular security reviews and updates