Cloudflare Workers

Deploying Cloudflare Workers: wrangler deploy, API deploy with bindings, version management, and secret rotation

Overview

SanMarcSoft uses Cloudflare Workers for edge computing. Workers are deployed via either wrangler deploy or the Cloudflare API directly. Key workers include the badges worker, URL shortener, and waitlist.

Prerequisites

  • Cloudflare API token: pass cloudflare/api-token
  • Cloudflare Account ID: pass cloudflare/account-id
  • Wrangler CLI: npm install -g wrangler or use project-local npx wrangler

Method 1: Wrangler Deploy (Preferred)

Standard Deploy

1
2
cd /path/to/worker
npx wrangler deploy

Wrangler reads wrangler.toml for configuration including bindings, routes, and compatibility settings.

Deploy with Environment

1
2
npx wrangler deploy --env production
npx wrangler deploy --env testing

Method 2: API Deploy (When Wrangler Fails)

Use the Cloudflare API directly when wrangler has issues with bindings or versioning. This is a multipart form upload.

Step 1: Prepare the Worker Script

Bundle the worker into a single file if needed:

1
npx esbuild src/index.ts --bundle --outfile=dist/worker.js --format=esm --target=es2022

Step 2: Create Metadata JSON

Create a metadata.json file with all bindings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "main_module": "worker.js",
  "bindings": [
    {
      "type": "d1",
      "name": "DB",
      "id": "<d1-database-id>"
    },
    {
      "type": "kv_namespace",
      "name": "CACHE",
      "id": "<kv-namespace-id>"
    },
    {
      "type": "secret_text",
      "name": "API_KEY",
      "text": ""
    }
  ],
  "compatibility_date": "2024-01-01",
  "compatibility_flags": []
}

Note: Secret bindings in metadata should have empty text values. Set actual secret values separately via the secrets API.

Step 3: Upload via API

1
2
3
4
5
6
7
8
9
CF_TOKEN=$(pass cloudflare/api-token)
ACCOUNT_ID=$(pass cloudflare/account-id)
WORKER_NAME="verifieddit-badges"

curl -s -X PUT \
  "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/workers/scripts/${WORKER_NAME}" \
  -H "Authorization: Bearer ${CF_TOKEN}" \
  -F "worker.js=@dist/worker.js;type=application/javascript+module" \
  -F "metadata=@metadata.json;type=application/json"

Setting Worker Secrets

Secrets are set independently of deployment. They persist across deploys.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Set a single secret
curl -s -X PUT \
  "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/workers/scripts/${WORKER_NAME}/secrets" \
  -H "Authorization: Bearer ${CF_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "SIGHTENGINE_API_USER",
    "text": "'"$(pass sightengine/api-user)"'",
    "type": "secret_text"
  }'

Bulk Secret Setting

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# List of secrets to set from pass store
for SECRET_NAME in SIGHTENGINE_API_USER SIGHTENGINE_API_SECRET CLERK_SECRET_KEY STRIPE_SECRET_KEY; do
  PASS_PATH="verifieddit/${SECRET_NAME,,}"  # lowercase pass path
  VALUE=$(pass "${PASS_PATH}")

  curl -s -X PUT \
    "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/workers/scripts/${WORKER_NAME}/secrets" \
    -H "Authorization: Bearer ${CF_TOKEN}" \
    -H "Content-Type: application/json" \
    --data "{\"name\": \"${SECRET_NAME}\", \"text\": \"${VALUE}\", \"type\": \"secret_text\"}"

  echo "Set ${SECRET_NAME}"
done

Version Management

Known Issue: Worker Versioning Conflicts

Cloudflare Workers have a versioning system that can cause deployment failures. When deploying via the API, you may encounter version conflicts if a previous deployment created versions.

Symptoms:

  • Deploy succeeds but old code still runs
  • API returns success but changes are not reflected
  • Dashboard shows multiple versions with “gradual rollout” percentages

Resolution:

  1. Check current versions:
1
2
curl -s "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/workers/scripts/${WORKER_NAME}/versions" \
  -H "Authorization: Bearer ${CF_TOKEN}" | jq '.result'
  1. If multiple versions exist with gradual rollout, set 100% traffic to the latest version:
1
2
3
4
5
6
VERSION_ID="<latest-version-id>"
curl -s -X PATCH \
  "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/workers/scripts/${WORKER_NAME}/versions/${VERSION_ID}" \
  -H "Authorization: Bearer ${CF_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"percentage": 100}'
  1. Alternatively, delete and recreate the worker (last resort):
1
2
3
4
5
6
7
# Delete
curl -s -X DELETE \
  "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/workers/scripts/${WORKER_NAME}" \
  -H "Authorization: Bearer ${CF_TOKEN}"

# Re-deploy
npx wrangler deploy

Checking Deployed Version

Add a debug endpoint to your worker to verify the running version:

1
2
3
4
5
6
7
// In your worker
if (url.pathname === '/__debug') {
  return new Response(JSON.stringify({
    version: '2026-03-21',
    deployed: new Date().toISOString(),
  }), { headers: { 'content-type': 'application/json' } });
}
1
curl -s https://worker-route.example.com/__debug | jq .

Route Configuration

Adding Routes via API

1
2
3
4
5
6
7
8
curl -s -X POST \
  "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/workers/routes" \
  -H "Authorization: Bearer ${CF_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{
    "pattern": "example.com/api/*",
    "script": "worker-name"
  }'

Listing Routes

1
2
curl -s "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/workers/routes" \
  -H "Authorization: Bearer ${CF_TOKEN}" | jq '.result[] | {pattern, script}'

Tail / Logging

1
2
3
4
5
6
# Live tail worker logs
npx wrangler tail <worker-name>

# With filters
npx wrangler tail <worker-name> --status error
npx wrangler tail <worker-name> --search "sightengine"

Troubleshooting

  • “Script not found” after deploy: Check that the worker name matches the route configuration. Worker names are case-sensitive.
  • Bindings missing after API deploy: Bindings must be included in every API deploy. Unlike secrets, bindings are not persisted separately.
  • D1 binding not working: Ensure the D1 database ID in metadata matches the actual database. Verify with npx wrangler d1 list.
  • Secrets cleared after deploy: Secrets should persist across deploys, but if deploying via API with a new script, re-set all secrets after deployment.