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.
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:
sf vcs autodeploy main=production dev=devonce.- Work on
dev. Every commit deploys thedevenvironment; test there against real infrastructure. - Check
sf deployments --env devandsf logs --env dev --errorsbefore promoting. sf vcs checkout main && sf vcs merge dev— production deploys itself.- If production breaks:
sf deploy --env production --commit @1, then fix forward ondev.
For a solo project, main mapped to production and honest use of sf build is usually enough.
Next
- Build and deploy — reading a deployment's events.
- Domains and secrets — per-environment configuration.
- Database — how migrations behave on a fresh environment.
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.