Environments

Staging and dev live inside the same app, not in a copy of it. How to create an environment, what's shared and what isn't, mapping branches to environments, promoting to production, and rolling one back.

The Spikefrost Team30 Jul 20264 min read

Every app supports multiple named environments — always production, plus whatever you add: staging, dev, test. They live inside the same app.

That last point is the whole design. One app, several running instances of it.

What's shared, what isn't

Shared across environments Separate per environment
Source code and file history Worker deployment
Agents and connectors Database
Version control (branches, commits) Key-value store
Secrets you set app-wide Durable Object state
Custom domains (attached per environment) Metrics dataset

So an environment is a place your code runs with its own data — not a fork of the project.

Creating one

There is no create step. Deploy to a name and it exists:

sf deploy --env staging

URLs follow a pattern: production is worker-<appId>.<domain>, and every other environment is <env>-<appId>.<domain>.

Never clone the app to get a staging copy. A clone is a permanently separate app: separate history, separate agents, code that diverges from the moment it's made. An environment is promotable and disposable, which is what you actually want.

Data in a new environment

A non-production database starts empty. Migrations apply per environment on deploy, so schema arrives with your first deploy — but rows don't. Seed what you need:

sf d1 execute --env staging "INSERT INTO products (name, price) VALUES ('Test', 100)"

That emptiness is a feature: staging can't accidentally serve real customer data.

Targeting an environment

Environment-sensitive commands take --env:

sf deploy --env staging
sf logs --env staging --since 1h
sf d1 select --env staging "SELECT COUNT(*) FROM leads"
sf deployments --env staging
sf analytics query --env staging "SELECT ..."

To scope a whole session, set the variable — the desktop app does this for you on a coding session:

export SF_APP_ENVIRONMENT=staging

An explicit --env always wins over it. In the desktop app, the environment selector in an app view scopes what you see — Agents, Schedule, and Data all follow it. If numbers look wrong, check the selector before you check your code.

Branch mapping and autodeploy

Attach branches to environments so commits ship themselves:

sf vcs autodeploy main=production dev=dev
sf vcs refs                    # read the current map

Now every commit that advances dev deploys the dev environment, and every commit that advances main deploys production. Each deploy is anchored to exactly that commit's tree.

This is convenient and it is sharp. Warn people — including agents — before committing untested work to a mapped branch.

On a mapped branch, a bare sf deploy commits and lets the mapping do the deploying.

Promotion is a merge

sf vcs checkout main
sf vcs merge dev

Promotion means merging into the production-mapped branch. Never deploy a side branch straight to production: if you do, production is running code that isn't reachable from the branch that's supposed to describe it, and the next main deploy silently reverts it.

Remember that after a merge the two branch heads legitimately differ — see Version control.

Rolling an environment back

sf deploy --env production --commit @3

That pins one environment to an older commit without touching the branch or any other environment. Use it when production is broken and you want the bleeding stopped before you diagnose.

To move the branch itself back, use sf vcs rollback — it creates a new commit, so it's reversible.

Note there's no automatic rollback on a failed deploy. Deploys are retry-forward by design.

Declared configuration per environment

Apps using declared configuration (agent-config/) get it reconciled per environment on deploy — agents materialize per-environment instances, and non-production routines default to disabled unless the file opts them in.

That default is deliberate: you rarely want staging's cron sending real email at 9am. Opt in explicitly, per environment, when you do.

A workflow that holds up

For a team shipping regularly:

  1. sf vcs autodeploy main=production dev=dev once.
  2. Work on dev. Every commit deploys the dev environment; test there against real infrastructure.
  3. Check sf deployments --env dev and sf logs --env dev --errors before promoting.
  4. sf vcs checkout main && sf vcs merge dev — production deploys itself.
  5. If production breaks: sf deploy --env production --commit @1, then fix forward on dev.

For a solo project, main mapped to production and honest use of sf build is usually enough.

Next

Frequently asked questions

How do I create a staging environment?

Deploy to it: sf deploy --env staging. The environment is created on first deploy. There is nothing to provision beforehand.

Should I clone the app to make a staging copy?

No. A clone is a permanently separate app that drifts from the original — different history, different agents, divergent code. An environment shares all of that and stays promotable.

Does staging share production's data?

No. Each environment has its own database, key-value store, and Durable Object state. A non-production database starts empty and is filled by that environment's migrations.

How do I promote staging to production?

Merge the branch into the production-mapped branch, normally main. Never deploy a side branch directly to production — the deployed state should always be reachable from the branch that maps to it.

How do I roll one environment back without touching the others?

Deploy an older commit to that environment: sf deploy --env staging --commit @3. The branch and the other environments are untouched.