Skip to content

Implementation

Option A end to end: the Cloudflare token, the Bitbucket variables and environments, the wrangler.toml layout, and the bitbucket-pipelines.yml that carries a change from a feature branch through dev and staging to production.

This is the implementation process for a Bitbucket-hosted Worker. It maps one to one onto the gate sequence in the QA and Deployment SOP. Where the SOP describes the GitHub Actions job, this describes the Bitbucket step that does the same work.

  • A Worker whose source lives in a Bitbucket repository.
  • Admin on that Bitbucket repository, to set variables and enable Pipelines.
  • Access to the Adventive Cloudflare account to mint a scoped API token and read the account ID.
  • Wrangler pinned as a dev dependency in the project (npm install --save-dev wrangler). Do not rely on a global install in CI.

The pipeline authenticates to Cloudflare with an API token, never a global key. Scope it to the minimum the deploy needs.

  1. In the Cloudflare dashboard, go to My Profile → API Tokens → Create Token.
  2. Start from the Edit Cloudflare Workers template, or build a custom token with these permissions:
    • Account → Workers Scripts → Edit
    • Zone → Workers Routes → Edit, scoped to the specific zones the Worker serves (adventive.dev, adventivestg.com, adventive.com as applicable). Do not grant all zones.
    • Account → Account Settings → Read (required for Wrangler to resolve the account).
    • If the Worker binds KV, R2, or D1, add Workers KV Storage → Edit, Workers R2 Storage → Edit, or D1 → Edit as needed.
  3. If the Worker binds a secret from Cloudflare Secrets Store (the platform default for runtime secrets), the token also needs Account → Secrets Store → Edit. Binding a secret to a Worker is a write against the secret; a read-only token fails at deploy time with an authorization error.
  4. Create the token and copy it once. You cannot retrieve it again.

Scope the token to the zones the Worker touches. An account-global token in a repository variable is a standing risk. The QA and Deployment SOP requires Workers Scripts:Edit plus the specific zones, nothing wider.

From the Cloudflare dashboard, Workers & Pages → Account details, copy the Account ID. It is not a secret, but the pipeline needs it as CLOUDFLARE_ACCOUNT_ID.

The pipeline does not carry runtime secrets. Worker runtime secrets (for example the New Relic license key) are created once at the account level in Cloudflare Secrets Store and bound by reference in wrangler.toml through [[secrets_store_secrets]] blocks. Never use wrangler secret put in a committed pipeline. The only credentials the pipeline holds are the API token and the account ID.

In the repository, go to Repository settings → Pipelines → Settings and toggle Enable Pipelines. This activates the bitbucket-pipelines.yml at the repo root.

2.2 Store credentials as secured variables

Section titled “2.2 Store credentials as secured variables”

Cloudflare credentials go in Bitbucket as variables, never in the repository.

For account-wide values, use Repository settings → Repository variables:

  • CLOUDFLARE_API_TOKEN — mark it Secured so it is masked in logs and not readable after saving.
  • CLOUDFLARE_ACCOUNT_ID — the account ID from step 1.2.

For per-environment separation, prefer Deployment variables (below) so the staging and production tokens differ and a staging build can never deploy to production.

Bitbucket Deployments give per-environment tracking, environment-scoped variables, and deployment gating. Go to Repository settings → Deployments and confirm the environment tiers exist: Test, Staging, and Production. Map them to Adventive’s dev, stg, and prd.

Under each environment, set environment-scoped Deployment variables. A production-scoped CLOUDFLARE_API_TOKEN visible only to production steps is the mechanism that stops a staging build from ever holding production credentials.

In Repository settings → Deployments, restrict the Production environment so only authorized reviewers can trigger a deployment to it. Combined with a manual trigger on the production step (Part 4), this is the Bitbucket equivalent of GitHub environment protection reviewers.

The pipeline deploys per environment with wrangler deploy --env <name>. The wrangler.toml declares one block per environment, matching the naming standard.

name = "adv-svc-example-prd"
main = "src/index.ts"
compatibility_date = "2026-07-01"
[env.dev]
name = "adv-svc-example-dev"
route = { pattern = "example.adventive.dev", zone_name = "adventive.dev" }
vars = { OTEL_ENABLED = "false", COMMIT_SHA = "local" }
[env.staging]
name = "adv-svc-example-stg"
route = { pattern = "example.adventivestg.com", zone_name = "adventivestg.com" }
vars = { OTEL_ENABLED = "true", COMMIT_SHA = "ci-placeholder" }
[env.production]
name = "adv-svc-example-prd"
route = { pattern = "example.adventive.com", zone_name = "adventive.com" }
vars = { OTEL_ENABLED = "true", COMMIT_SHA = "ci-placeholder" }

COMMIT_SHA is a literal placeholder in the file. The pipeline overrides it at deploy time so the deployed Worker’s health endpoint reports the real commit. Runtime secrets are added as [[secrets_store_secrets]] blocks per environment, not shown here; see the Secrets and Security Policy.

Every custom hostname needs its proxied AAAA record at 100:: before the Worker deploys to that route. Pre-create the DNS record, or the first request returns a 1014 error. This is a platform rule, independent of the CI provider.

bitbucket-pipelines.yml at the repo root. Branch and tag mapping:

  • Any feature branch and every pull request run the quality gate and a dry-run deploy. Nothing ships.
  • develop deploys to dev after the gate.
  • main deploys to stg after the gate, then smoke-tests staging.
  • A v*.*.* tag deploys to prd behind a manual gate, smoke-tests production, and rolls back automatically on smoke failure.
image: node:20
definitions:
caches:
npm: ~/.npm
steps:
- step: &quality
name: Quality gate
caches: [npm]
script:
- npm ci
- npm run lint
- npm run typecheck
- npm run test
- bash scripts/preflight-secrets.sh
- npx wrangler deploy --dry-run --env staging
artifacts:
- dist/**
pipelines:
pull-requests:
"**":
- step: *quality
branches:
develop:
- step: *quality
- step:
name: Deploy dev
deployment: test
caches: [npm]
script:
- npm ci
- npx wrangler deploy --env dev --var COMMIT_SHA:$BITBUCKET_COMMIT
- bash scripts/smoke.sh https://example.adventive.dev $BITBUCKET_COMMIT
main:
- step: *quality
- step:
name: Deploy staging
deployment: staging
caches: [npm]
script:
- npm ci
- npx wrangler deploy --env staging --var COMMIT_SHA:$BITBUCKET_COMMIT
- bash scripts/smoke.sh https://example.adventivestg.com $BITBUCKET_COMMIT
tags:
"v*.*.*":
- step: *quality
- step:
name: Deploy production
deployment: production
trigger: manual
caches: [npm]
script:
- npm ci
- npx wrangler deploy --env production --var COMMIT_SHA:$BITBUCKET_COMMIT
- bash scripts/smoke.sh https://example.adventive.com $BITBUCKET_COMMIT

The &quality YAML anchor defines the gate once and reuses it, so every path runs the identical gate. $BITBUCKET_COMMIT is Bitbucket’s built-in commit SHA. trigger: manual holds the production step until an authorized reviewer clicks deploy, which pairs with the Production environment restriction from step 2.4.

Bitbucket has no direct equivalent of the GitHub if: failure() step, so guard the rollback with shell. Replace the production script with a form that rolls back if the smoke test fails:

script:
- npm ci
- npx wrangler deploy --env production --var COMMIT_SHA:$BITBUCKET_COMMIT
- |
if ! bash scripts/smoke.sh https://example.adventive.com $BITBUCKET_COMMIT; then
npx wrangler rollback --env production --message "automatic rollback from CI after smoke failure"
exit 1
fi

Rollback is containment, not a fix. After any automatic rollback, follow the rollback procedure in the QA and Deployment SOP: open an incident, confirm metrics returned to baseline, and leave staging on the broken version so the fix is developed against the real failure.

SOP stepBitbucket stepCommand
InstallQuality gatenpm ci
LintQuality gatenpm run lint
TypecheckQuality gatenpm run typecheck
Unit testsQuality gatenpm run test
Secret scanQuality gatebash scripts/preflight-secrets.sh
Dry-run deployQuality gatenpx wrangler deploy --dry-run --env staging
DeployDeploy dev/staging/productionnpx wrangler deploy --env <name>
Smoke testafter each real deploybash scripts/smoke.sh <url> <sha>

Steps run in order; an early failure short-circuits the rest. A red gate does not merge or deploy.

  • dev is the shared development environment, deployed from develop. Per-engineer local iteration still uses wrangler dev and does not go through the pipeline.
  • stg deploys automatically on merge to main.
  • prd deploys only from a v*.*.* tag, behind the manual gate.

Between staging and production, the change observes the staging soak window from the QA and Deployment SOP, sized by change type and Worker tier (15 minutes for config-only up to 24 hours for tier-1 or any D1 schema change). No soak, no promotion. During soak, the owning team watches Workers analytics and Logpush for new error classes.

- [ ] Wrangler pinned as a dev dependency
- [ ] wrangler.toml has [env.dev], [env.staging], [env.production] with correct routes and zones
- [ ] Proxied AAAA record at 100:: exists for every custom hostname
- [ ] Runtime secrets created in Cloudflare Secrets Store and bound in wrangler.toml
- [ ] Scoped API token created (Workers Scripts:Edit + specific zones + Secrets Store:Edit if bound)
- [ ] CLOUDFLARE_API_TOKEN stored Secured; production token scoped to the Production environment
- [ ] CLOUDFLARE_ACCOUNT_ID set
- [ ] Bitbucket Pipelines enabled
- [ ] Deployment environments mapped (Test/Staging/Production to dev/stg/prd)
- [ ] Production environment restricted to authorized reviewers
- [ ] scripts/preflight-secrets.sh and scripts/smoke.sh present and executable
- [ ] bitbucket-pipelines.yml committed
- [ ] A test change confirms: gate on PR, deploy on develop/main, gated deploy on tag