Skip to content

Stand up staging.dmarc.mx + promote migrations through staging before prod #195

@schmug

Description

@schmug

Why

Today every change — including D1 schema migrations — flies straight to prod via the Cloudflare Git integration. With Pro subscriptions about to go live (#185 unblocks), the cost of a broken migration goes from "Cory notices and fixes" to "paying customer sees an outage." The expand-contract migration discipline already in CLAUDE.md is good, but it's a convention, not a backstop.

This issue stands up a minimum-viable staging environment that costs ~$0/mo and adds ~30 min of one-time setup, then makes migrations land in staging before prod.

Out of scope: gradual / versioned rollouts (`wrangler versions deploy`) — that's a separate track for risky code changes, filed separately if/when needed. This issue is specifically about the schema safety net.

Scope

1. Cloudflare resources

  • Create D1 database `dmarcheck-db-staging`
  • Add DNS: `staging.dmarc.mx` → Worker (Cloudflare DNS, no extra cost)
  • Create staging WorkOS environment (sandbox tier, free)
  • Reuse existing Stripe test mode keys (no new account needed)
  • Cloudflare Web Analytics: do NOT add a token for staging — keep beacon off there

2. `wrangler.toml`

Add an `[env.staging]` block:

```toml
[env.staging]
name = "dmarcheck-staging"
route = { pattern = "staging.dmarc.mx/*", zone_name = "dmarc.mx" }

[[env.staging.d1_databases]]
binding = "DB"
database_name = "dmarcheck-db-staging"
database_id = ""
```

Wrangler secrets to set on the staging env (separate from prod):

  • `WORKOS_API_KEY` / `WORKOS_CLIENT_ID` (sandbox)
  • `STRIPE_SECRET_KEY` / `STRIPE_WEBHOOK_SECRET` / `STRIPE_PRICE_ID_PRO` (test mode)
  • `SESSION_SECRET` (different from prod — staging sessions must not be valid in prod)
  • (No `CF_ANALYTICS_TOKEN` — keep beacon off staging)

3. Sentry

  • Don't create a separate Sentry project — set `environment: "staging"` in the SDK init based on a build-time flag or a `STAGING=1` env var
  • Optionally bump sample rate to 1.0 in staging (everything is interesting there)

4. Migration promotion workflow

Update `.github/workflows/migrate.yml` so the safe path is staging-first:

```
push to main
→ CI passes
→ migrate.yml runs:
step 1: wrangler d1 migrations apply dmarcheck-db-staging --remote
step 2: GET https://staging.dmarc.mx/health (must 200)
step 3: wait for manual approval (environments: prod)
step 4: wrangler d1 migrations apply dmarcheck-db --remote
```

Implementation notes:

  • Use GitHub Environments with required reviewers on the `prod` environment for the approval gate. Cory is the only reviewer; one click promotes.
  • Steps 1-2 run unattended; if step 2 fails, step 4 never runs and main stays partially deployed (which is fine because of the additive-only migration discipline already in CLAUDE.md).
  • The Cloudflare Git integration deploys the code to prod independently — the migration workflow only gates the schema. Code-vs-schema ordering still relies on the existing additive-only discipline.

5. README + CLAUDE.md updates

  • Add a "Staging" section under Database migrations describing the new flow
  • Document that `staging.dmarc.mx` is a non-public testing surface (`noindex` enforced) and that anyone who finds it should not file issues against it
  • Add a `<meta name="robots" content="noindex,nofollow">` injection on staging via an env-var check in `src/views/html.ts`

6. CI: deploy staging on every main commit

The Cloudflare Git integration currently deploys main → prod. Add a parallel auto-deploy for staging via either:

  • A second connection in the Cloudflare dashboard targeting the `env.staging` config, or
  • A GitHub Actions workflow that runs `wrangler deploy --env staging` on push to main (uses a Workers Edit token, scope-restricted)

The first option is lower-friction; the second gives us versioned-deploy headroom if we want `wrangler versions deploy` later.

Acceptance criteria

  • `https://staging.dmarc.mx\` returns 200, serves the same UI as prod, with a visible "STAGING" banner (suggest: red ribbon top-of-page in `src/views/html.ts` keyed off env)
  • `https://staging.dmarc.mx/api/check?domain=dmarc.mx\` returns the same shape as prod
  • A test Stripe Checkout against staging completes end-to-end with a 4242 test card and unlocks Pro on the staging account
  • A new migration PR demonstrates the gated flow: applies to staging → smoke passes → approval gate → applies to prod
  • `robots.txt` and the `` tag both noindex staging
  • Sentry events from staging arrive tagged `environment: staging`

Estimate

~30–45 min of one-time Cloudflare/WorkOS/GitHub config + ~50–80 lines across `wrangler.toml`, `migrate.yml`, `html.ts`, README, CLAUDE.md. Single PR.

Follow-ups (separate issues, not part of this)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions