tessl install https://github.com/secondsky/claude-skills --skill workers-ci-cdgithub.com/secondsky/claude-skills
Complete CI/CD guide for Cloudflare Workers using GitHub Actions and GitLab CI. Use for automated testing, deployment pipelines, preview environments, secrets management, or encountering deployment failures, workflow errors, environment configuration issues.
Review Score
87%
Validation Score
12/16
Implementation Score
85%
Activation Score
90%
Status: ✅ Production Ready | Last Verified: 2025-01-27 GitHub Actions: v4 | GitLab CI: Latest | Wrangler: 4.50.0
Automated testing and deployment of Cloudflare Workers using GitHub Actions or GitLab CI. Enables running tests on every commit, deploying to preview/staging/production environments automatically, managing secrets securely, and implementing deployment gates for safe releases.
Key capabilities: Automated testing, multi-environment deployments, preview URLs per PR, secrets management, deployment verification, automatic rollbacks.
GitHub Actions Updates (January 2025):
cloudflare/wrangler-action@v4 (improved caching, faster deployments)vars and secrets parametersapiToken renamed to api-token (kebab-case)Migration from v3:
# ❌ OLD (v3)
- uses: cloudflare/wrangler-action@3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
# ✅ NEW (v4)
- uses: cloudflare/wrangler-action@v4
with:
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}Wrangler 4.50.0 (January 2025):
--dry-run flag for deployment validation--keep-vars to preserve environment variables1. Create Cloudflare API Token
Go to: https://dash.cloudflare.com/profile/api-tokens
Create token with permissions:
2. Add Secret to GitHub
Repository → Settings → Secrets → Actions → New repository secret:
CLOUDFLARE_API_TOKEN3. Create .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy to Cloudflare Workers
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install
- run: bun test
- name: Deploy
uses: cloudflare/wrangler-action@v4
with:
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy4. Push and Verify
git add .github/workflows/deploy.yml
git commit -m "Add CI/CD pipeline"
git pushCheck Actions tab on GitHub to see deployment progress.
✅ CORRECT:
# Use GitHub Secrets
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}❌ WRONG:
# ❌ NEVER hardcode tokens
api-token: "abc123def456..."Why: Exposed tokens allow anyone to deploy to your account.
✅ CORRECT:
- run: bun test # ✅ Tests run first
- name: Deploy
uses: cloudflare/wrangler-action@v4
with:
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}❌ WRONG:
# ❌ Skipping tests
- name: Deploy
uses: cloudflare/wrangler-action@v4
# No tests!Why: Broken code shouldn't reach production.
✅ CORRECT:
# Production (main branch)
- name: Deploy to Production
if: github.ref == 'refs/heads/main'
run: bunx wrangler deploy --env production
# Staging (other branches)
- name: Deploy to Staging
if: github.ref != 'refs/heads/main'
run: bunx wrangler deploy --env staging❌ WRONG:
# ❌ Always deploying to production
- run: bunx wrangler deployWhy: Test changes in staging before production.
✅ CORRECT:
- name: Deploy
id: deploy
uses: cloudflare/wrangler-action@v4
- name: Verify Deployment
run: |
curl -f https://your-worker.workers.dev/health || exit 1❌ WRONG:
# ❌ No verification
- name: Deploy
uses: cloudflare/wrangler-action@v4
# Assuming it worked...Why: Deployments can fail silently (DNS issues, binding errors).
✅ CORRECT:
deploy-production:
environment:
name: production
url: https://your-worker.workers.dev
# Requires manual approval❌ WRONG:
# ❌ Auto-deploy to production without review
deploy-production:
runs-on: ubuntu-latestWhy: Human review catches issues automation misses.
Recommended setup:
main branch → production environmentwrangler.jsonc:
{
"name": "my-worker",
"main": "src/index.ts",
"env": {
"production": {
"name": "my-worker-production",
"vars": {
"ENVIRONMENT": "production"
}
},
"staging": {
"name": "my-worker-staging",
"vars": {
"ENVIRONMENT": "staging"
}
}
}
}Types of configuration:
Setting secrets:
# Local development
wrangler secret put DATABASE_URL
# CI/CD (via GitHub Actions)
bunx wrangler secret put DATABASE_URL --env production <<< "${{ secrets.DATABASE_URL }}"Automatically deploy each PR to a unique URL for testing:
- name: Deploy Preview
uses: cloudflare/wrangler-action@v4
with:
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --env preview-${{ github.event.number }}Each PR gets URL like: my-worker-preview-42.workers.dev
name: Deploy Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun test
- run: bun run build
- name: Deploy to Production
uses: cloudflare/wrangler-action@v4
with:
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --env productionname: Preview
on:
pull_request:
branches: [main]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun test
- name: Deploy Preview
id: deploy
uses: cloudflare/wrangler-action@v4
with:
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --env preview-${{ github.event.number }}
- 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://my-worker-preview-${{ github.event.number }}.workers.dev'
})name: Test
on:
push:
branches: ['**']
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun test --coverage
- name: Upload Coverage
uses: codecov/codecov-action@v4
with:
files: ./coverage/lcov.infoname: Deploy Production (Manual Approval)
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://my-worker.workers.dev
# Requires manual approval in GitHub Settings
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun test
- name: Deploy
uses: cloudflare/wrangler-action@v4
with:
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --env productionname: Canary Deployment
on:
workflow_dispatch:
inputs:
percentage:
description: 'Traffic percentage to new version'
required: true
default: '10'
jobs:
canary:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
# Deploy to canary environment
- name: Deploy Canary
uses: cloudflare/wrangler-action@v4
with:
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --env canary
# Configure traffic split via Cloudflare API
# (See references/deployment-strategies.md for full example)Use semantic commit messages:
feat: add user authentication
fix: resolve rate limiting issue
chore: update dependenciesRun linting and type checking:
- run: bun run lint
- run: bun run type-check
- run: bun testCache dependencies:
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
# Bun automatically caches dependenciesDeploy different branches to different environments:
- name: Deploy
run: |
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
bunx wrangler deploy --env production
else
bunx wrangler deploy --env staging
fiMonitor deployments:
- name: Notify Slack
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{"text": "Deployment failed: ${{ github.sha }}"}Error: A valid Cloudflare API token is requiredCause: Missing or invalid CLOUDFLARE_API_TOKEN secret.
Fix:
api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}Error: Not enough permissions to deployCause: API token lacks required permissions.
Fix: Recreate token with:
Error: wrangler.toml not foundCause: Missing wrangler configuration.
Fix: Ensure wrangler.jsonc exists in repository root.
Cause: Missing secrets or environment variables.
Fix: Set secrets in CI:
- name: Set Secrets
run: |
echo "${{ secrets.DATABASE_URL }}" | bunx wrangler secret put DATABASE_URL --env productionCause: Environment differences (Node version, missing dependencies).
Fix:
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest # Lock version
- run: bun install --frozen-lockfile # Use exact versionsCause: Multiple PRs deploying to same preview environment.
Fix: Use PR number in environment name:
command: deploy --env preview-${{ github.event.number }}Cause: Echoing secrets in workflow.
Fix:
# ❌ WRONG
- run: echo "Token: ${{ secrets.API_TOKEN }}"
# ✅ CORRECT
- run: echo "Deploying..." # No secrets in outputLoad reference files for detailed, specialized content:
Load references/github-actions.md when:
Load references/gitlab-ci.md when:
Load references/deployment-strategies.md when:
Load references/secrets-management.md when:
Load templates/github-actions-full.yml for:
Load templates/gitlab-ci-full.yml for:
Load templates/preview-deployment.yml for:
Load templates/rollback-workflow.yml for:
Load scripts/verify-deployment.sh for:
For deployment testing, load:
This skill focuses on CI/CD automation for ALL Workers deployments regardless of bindings used.
Questions? Load references/secrets-management.md or use /workers-deploy command for guided deployment.