Getting Started

Adding staging

Optional — add a pre-production context between `development` and production, with its own Workers, data and branch.

Staging is optional. It's a pre-production copy of your project, with its own Workers, bucket, content database, pool and Postgres, deployed from a staging branch so you can check a promotion before it reaches production. Add it when you want that rehearsal, or skip this page and promote development straight to main. Skipping costs nothing: only a push to a staging branch deploys staging, so nothing reads its value files until one exists.

flowchart LR
    L["1. Local setup<br/>Both apps on your machine"] --> D["2. Deploying development<br/>A push deploys"]
    L --> R["3. Adding pull request previews<br/>Independent of development"]
    D --> S["**4. Adding staging**<br/>Optional"]
    S --> P["5. Going to production<br/>Your domain, real users"]
    D -. "without staging" .-> P

Prerequisites

You needHow to get itRequired
Deploying development finisheddevelopment deployingYes
A Postgres database for stagingAny managed Postgres reachable from the internet, never one another context usesYes
Google, GitHub, and Polar credentials for stagingSet up sign-in and payments — a separate client per contextOnly for the feature that reads them

The repository needs no new secret or variable. The push identity already reads the staging tier, and the Cloudflare token already creates every resource type staging binds.

Add the context

1. Fill staging's Secret Tier

staging reads the staging environment at / in apps/web's Infisical project. Store the connection string there:

infisical secrets set DATABASE_URL='<the staging connection string>' \
  --env=staging --path=/ --projectId='<apps/web project id>'

Then add staging's own value for every key that Set up your secret store put in /local. A feature you're skipping takes a stand-in, as it does locally.

2. Write staging's values

Staging's origins take the same shape as development's, with its own context in them. Its client IDs and contact address ship empty too:

apps/web/.env.staging
NUXT_PUBLIC_APP_BASE_URL=https://<slug>-web-staging.<subdomain>.workers.dev
NUXT_PUBLIC_APP_WEBSITE_BASE_URL=https://<slug>-site-staging.<subdomain>.workers.dev
GOOGLE_CLIENT_ID=<staging's Google client ID>
GITHUB_CLIENT_ID=<staging's GitHub client ID>
apps/site/.env.staging
NUXT_PUBLIC_APP_BASE_URL=https://<slug>-site-staging.<subdomain>.workers.dev
NUXT_PUBLIC_CONTACT_EMAIL=hello@example.com

Commit both on development and push.

3. Create the staging branch

From here promotion runs development → staging → main. Create the middle branch:

git checkout -b staging development
git push -u origin staging

That first push creates staging's Workers, bucket, content database and pool, each named <slug>-<app>-staging, and deploys both applications. Every later push to staging deploys it again. Nothing enforces the order — the gate reads only the branch pushed — so promote by merging forward.

4. Onboard staging's sending domains

Staging sends from its own hostnames, <slug>-<app>-staging.<zone>. Onboard each one the way Email will not send until you onboard a domain walks it, reading the hostname with APP_RUNTIME_CONTEXT=staging.

A red run reads the same way it did for development: When it does not work lists the causes.

Next steps

  • Going to production — the last stage, promoted through staging, once the application and its name are final
  • Resources — what staging's first push created, and how to delete a context's resources if you drop it later

Copyright © 2026