Zero to Production:
CI/CD for Cloudflare Workers
GitHub Actions workflows, Wrangler deployments, environment management, and rollback strategies for 28 production workers.
Deploying one Worker is easy. Deploying 28 Workers across three environments with zero downtime, automatic rollbacks, and proper secret management? That requires a real CI/CD pipeline.
This is the exact deployment infrastructure running in productionโfrom git push to global edge deployment in under 90 seconds.
The Pipeline
GitHub Actions Workflow
The main deployment workflow handles linting, testing, and deploying to the correct environment based on branch:
name: Deploy Workers
on:
push:
branches: [main, staging, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm run test
deploy:
needs: lint-and-test
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- name: Determine Environment
id: env
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "environment=production" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref }}" == "refs/heads/staging" ]]; then
echo "environment=staging" >> $GITHUB_OUTPUT
else
echo "environment=development" >> $GITHUB_OUTPUT
fi
- name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: deploy --env ${{ steps.env.outputs.environment }}
Environment Configuration
Each environment has its own configuration in wrangler.toml:
name = "api-gateway"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# Shared bindings
[[kv_namespaces]]
binding = "CACHE"
# Development
[env.development]
name = "api-gateway-dev"
vars = { ENVIRONMENT = "development", LOG_LEVEL = "debug" }
[[env.development.kv_namespaces]]
binding = "CACHE"
id = "abc123-dev"
# Staging
[env.staging]
name = "api-gateway-staging"
vars = { ENVIRONMENT = "staging", LOG_LEVEL = "info" }
[[env.staging.kv_namespaces]]
binding = "CACHE"
id = "abc123-staging"
# Production
[env.production]
name = "api-gateway"
vars = { ENVIRONMENT = "production", LOG_LEVEL = "warn" }
routes = [{ pattern = "api.proptechusa.ai/*", zone_name = "proptechusa.ai" }]
[[env.production.kv_namespaces]]
binding = "CACHE"
id = "abc123-prod"
Secrets Management
Never commit secrets. Use Wrangler to set them per environment:
#!/bin/bash
# Set secrets for each environment
for env in development staging production; do
echo "Setting secrets for $env..."
wrangler secret put ANTHROPIC_API_KEY --env $env
wrangler secret put SLACK_WEBHOOK_URL --env $env
wrangler secret put DATABASE_URL --env $env
echo "โ $env secrets configured"
done
| Secret | Dev | Staging | Production |
|---|---|---|---|
ANTHROPIC_API_KEY |
Test key | Test key | Production key |
SLACK_WEBHOOK_URL |
#dev-alerts | #staging-alerts | #production-alerts |
DATABASE_URL |
D1 dev DB | D1 staging DB | D1 prod DB |
Multi-Worker Deployments
With 28 workers, we use a matrix strategy to deploy in parallel:
jobs:
deploy-workers:
runs-on: ubuntu-latest
strategy:
matrix:
worker:
- api-gateway
- lead-processor
- valuation-engine
- notify-slack
- notify-email
- ai-chatbot
- analytics
- webhook-receiver
# ... 20 more workers
fail-fast: false # Don't stop on single failure
steps:
- uses: actions/checkout@v4
- name: Deploy ${{ matrix.worker }}
working-directory: workers/${{ matrix.worker }}
run: npx wrangler deploy --env production
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
fail-fast: false ensures one failing worker doesn't block others.Rollback Strategy
Every deployment needs a rollback plan. Cloudflare keeps previous versions:
#!/bin/bash
# Rollback to previous version
WORKER_NAME=$1
VERSION=$2 # Optional: specific version, defaults to previous
if [ -z "$VERSION" ]; then
# Get previous deployment
VERSION=$(wrangler deployments list --name $WORKER_NAME | head -2 | tail -1 | awk '{print $1}')
fi
echo "Rolling back $WORKER_NAME to $VERSION..."
wrangler rollback --name $WORKER_NAME --version $VERSION
# Notify Slack
curl -X POST $SLACK_WEBHOOK -d "{\"text\":\"โ ๏ธ Rollback: $WORKER_NAME โ $VERSION\"}"
Health Checks
Post-deployment verification ensures workers are healthy:
post-deploy-check:
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Wait for propagation
run: sleep 10
- name: Health check
run: |
response=$(curl -s -o /dev/null -w "%{http_code}" https://api.proptechusa.ai/health)
if [ "$response" != "200" ]; then
echo "Health check failed with status $response"
exit 1
fi
echo "โ Health check passed"
- name: Smoke test
run: |
# Test critical endpoints
curl -f https://api.proptechusa.ai/v1/status
curl -f https://api.proptechusa.ai/v1/valuation/health
echo "โ Smoke tests passed"
- name: Notify success
if: success()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-d '{"text":"โ
Deployment successful: ${{ github.sha }}"}'
- name: Auto-rollback on failure
if: failure()
run: |
./scripts/rollback.sh api-gateway
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-d '{"text":"๐จ Auto-rollback triggered: ${{ github.sha }}"}'
Production Checklist
- Branch protection on main (require PR reviews)
- Secrets stored in GitHub Secrets, never committed
- Separate KV/D1/R2 namespaces per environment
- Health checks after every deployment
- Automatic rollback on failure
- Slack notifications for deploys and failures
- Matrix deployments for parallel worker updates
- Deployment logs retained for debugging
A good CI/CD pipeline isn't about automationโit's about confidence. Every push should either succeed completely or fail safely with automatic recovery.
Related Articles
Need CI/CD Setup for Your Workers?
We build production deployment pipelines that scale.
โ Get Started