Skip to content

Deploy to production

For: putting a build in front of real players, on grafted-wars.com.

Production is the worker named grafted-wars-staging. That name is the reverse of what it reads like, and the actual staging deployment is grafted-wars-true-staging. Check which is which by the hostname it answers on, never by the name in the dashboard.

Production is never wiped. It has no reset switch, and schema changes reach it as migrations.

Preconditions

  • [ ] The build has been through staging and looked right there.
  • [ ] The gate is green on the commit being deployed (npm run check).
  • [ ] CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are set, in the environment or the repository's gitignored .env file.
  • [ ] An owner token exists at ~/.grafted-wars/owner-token, if the claim route is to work.

The two arming signals

An ordinary npm run deploy:prod never touches production. The script refuses unless both arming signals are present, and exits 3 when the flag is absent.

bash
GW_FLIP_ACK=grafted-wars.com npm run deploy:prod -- --flip

The environment variable must match the domain. The flag must be passed after the double dash so npm forwards it to the script rather than consuming it.

Steps

  1. Deploy.

    bash
    GW_FLIP_ACK=grafted-wars.com npm run deploy:prod -- --flip
    • Expect: a build, a wrangler upload, then one domain-binding call per hostname, then a summary naming the worker and its bindings.
    • Expect: a curl command printed at the end. The script names it and does not run it, because local DNS can answer the old address for a while.
  2. On the very first deploy of this worker only, give it its admin secret.

    bash
    npx wrangler secret put ADMIN_EMAIL --name grafted-wars-staging
    • An ordinary deploy preserves an existing secret. It is lost only if the worker itself is deleted and recreated.
  3. Check the live edge with the command the script printed.

    bash
    curl https://grafted-wars.com/api/health
    • Expect: a JSON body reporting ok, the worker name and the version it stamped.
    • If instead every request is refused: the ADMIN_EMAIL secret is missing on this worker.
  4. Check www answers too, since it is a separate binding.

    bash
    curl https://www.grafted-wars.com/api/health

What production leaves off

The configuration file's defaults are the production-safe ones, and the production deploy does not override them.

SettingProduction
Public signupon, narrowed to the addresses GW_SIGNUP_ALLOW names
Admin routesoff
Dense seedoff, so the universe fills as accounts join
Clock ratereal time
Reset switchnone exists

Because admin routes are off, the export and overview endpoints do not answer in production. Data and stores covers what that means for reading live state.

Who may sign up

Production answers /api/auth/signup, so a player joins by making an account rather than by being minted one. Which addresses it will seat is a separate question, answered by the GW_SIGNUP_ALLOW secret: a comma-separated list of email addresses, matched trimmed and case-folded the way every other address is.

bash
npx wrangler secret put GW_SIGNUP_ALLOW --name grafted-wars-staging
  • A secret rather than a variable in wrangler.jsonc, because the list is made of real people's addresses and nothing commits those.
  • With the secret unset, an open signup accepts anyone who finds the domain. Narrowing it is the only thing standing between a public hostname and a stranger's account.
  • The list narrows an open signup and never opens a closed one. To close the route entirely, deploy with GW_PUBLIC_SIGNUP=0 in the deploying environment.
  • A refused address gets a 403 and EMAIL_NOT_ALLOWED; the sign-up screen states it plainly.

What is irreversible

The domain binding is the part that reaches players. Once grafted-wars.com points at the new worker, everyone is on it.

There is no rollback command in this tooling. Undoing a bad deploy means checking out a known-good commit and running the same deploy command again. Plan for that being the only route before you flip.

Rerunning the same command is safe. The upload re-sends the same build, and each binding call overrides an existing origin, so repeating a correct deploy changes nothing.

When a deploy half-lands

The upload and the domain bindings are separate calls. When the upload succeeds and a binding call then fails, one hostname can be bound while the other is not.

The script exits 1 in that case and does not undo the binding that already succeeded. The recovery is to fix the cause and run the same command again, which re-issues both bindings.

The zone

The script reads CLOUDFLARE_ZONE_ID when it is set, and otherwise falls back to a zone identifier built into it. Setting it is only needed when the zone changes.