Back to Blog
·4 min read

Setting up a Cloudflare Workers CI/CD pipeline from scratch

Building a complete deployment pipeline for Cloudflare Workers using GitHub Actions and Wrangler CLI.

AI Dev
cloudflare
cicd
deployment
workers

Setting up a Cloudflare Workers CI/CD pipeline from scratch

I've been deploying Workers manually for too long. Every time I made a change, I'd run wrangler publish from my terminal and hope nothing broke. That works fine for side projects, but when you're shipping to production, you need a proper CI/CD pipeline.

Here's how I set up automated deployments that actually work.

The Goal

I wanted a pipeline that:

  • Runs tests on every push
  • Deploys to staging automatically on main branch
  • Requires manual approval for production deployments
  • Handles secrets and environment variables properly

Setting Up Wrangler Authentication

First, you need to authenticate Wrangler in your CI environment. The cleanest approach is using API tokens instead of your global API key.

Go to the Cloudflare dashboard → My Profile → API Tokens → Create Token. Use the "Custom token" template with these permissions:

  • Zone:Zone:Read (for your domain)
  • Zone:Zone Settings:Read
  • Account:Cloudflare Workers:Edit

Store this token in your GitHub repository secrets as CLOUDFLARE_API_TOKEN.

The GitHub Actions Workflow

Here's the complete workflow file I use (.github/workflows/deploy.yml):

name: Deploy Worker
 
on:
  push:
    branches: [main, staging]
  pull_request:
    branches: [main]
 
env:
  NODE_VERSION: '20'
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - run: npm ci
      - run: npm run test
      - run: npm run lint
 
  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/staging'
    environment: staging
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - run: npm ci
      - run: npm run build
      
      - name: Deploy to staging
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          environment: 'staging'
 
  deploy-production:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: production
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - run: npm ci
      - run: npm run build
      
      - name: Deploy to production
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          environment: 'production'

Managing Multiple Environments

The key to this setup is using Wrangler's environment support. Your wrangler.toml should define different environments:

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-15"
 
[env.staging]
name = "my-worker-staging"
route = "staging.myapp.com/*"
 
[env.production]
name = "my-worker-production"
route = "myapp.com/*"
 
[[env.staging.kv_namespaces]]
binding = "MY_KV"
id = "staging-kv-id"
 
[[env.production.kv_namespaces]]
binding = "MY_KV" 
id = "production-kv-id"

This gives you complete isolation between environments -- different Worker names, routes, and even different KV namespaces.

Handling Secrets

For secrets, I use Wrangler's secret management rather than trying to pass them through the workflow. Set them up once per environment:

# Staging secrets
wrangler secret put API_KEY --env staging
 
# Production secrets  
wrangler secret put API_KEY --env production

The deployment will automatically use the correct secrets for each environment.

Adding Manual Approval

GitHub's environment protection rules let you require manual approval for production deployments. Go to your repository settings → Environments → Create "production" environment → Add required reviewers.

Now production deployments will pause and wait for approval, while staging deployments happen automatically.

Testing the Pipeline

I use Vitest for my Worker tests since it has great TypeScript support and works well with Workers' runtime APIs:

import { describe, it, expect } from 'vitest'
import worker from '../src/index'
 
describe('Worker', () => {
  it('responds with hello world', async () => {
    const request = new Request('http://localhost/')
    const response = await worker.fetch(request)
    
    expect(response.status).toBe(200)
    expect(await response.text()).toContain('Hello World')
  })
})

The pipeline runs these tests before any deployment, catching issues early.

What I Learned

  • Environment separation is crucial -- Do not share KV namespaces or secrets between staging and production
  • Manual approval saves you -- I've caught issues in staging that would have broken production
  • Keep secrets in Wrangler -- It's more secure than passing them through GitHub Actions
  • Test everything -- The pipeline is only as good as your test coverage

This setup has saved me from shipping broken code multiple times. The few minutes to set it up properly pays dividends when you're moving fast and need confidence in your deployments.