Documentation

Environments

Separate copies of one project, and the promotion that moves configuration between them.

An environment is a full copy of a project — its own users, sessions, identities, providers, templates and keys. Nothing is shared. A user created in develop does not exist in production, and never will, because they were never in that store.

What does move between environments is configuration, and only when you promote it deliberately.

One project, several issuers

Each environment answers on its own issuer URL, derived from the project slug:

organization
└── project · acme
    ├── environment · main      → acme.nulvless.co
    ├── environment · staging   → acme-staging.nulvless.co
    └── environment · develop   → acme-develop.nulvless.co

Production's slug is main, and it is the one environment whose URL carries no suffix. The console displays it as "Production"; every other environment shows its raw slug, which is why the same environment can read Develop in a picker and develop in a sentence.

The issuer URL is the first argument to createClient. Pointing an app at another environment is a URL change and a key change — not a code change. See Clients.

Creating one

From the environment picker in the console header, or from Others → Environments. An environment takes a name and a slug:

namestringRequired

The label you read in the console. Free text.

slugstringRequired

The identifier in the URL, and what the merge screens address it by. Choosing staging gives you acme-staging.nulvless.co.

A new environment builds before it can serve traffic:

buildingstatus

Being provisioned. Not yet usable.

readystatus

Serving on its issuer URL.

errorstatus

Provisioning failed.

Keys are per environment, so a fresh environment starts with its own publishable and secret keys. Nothing you configured elsewhere comes with it — which is the point, and also the first surprise.

Comparing before merging

Promotion starts as a diff, not a write. The Compare screen takes an origin and a destination and shows every setting that differs:

Compare environments

What would change if you promoted development to production.

developmentproductionPromote
MFA requiredfalsetrue
OAuth providersGoogle, GithubGoogle, Github, Apple
Sign-in rate limit100 / min20 / min
Allowed email domains—acme.com
Refresh token enabledfalsetrue
Communication providersMailtrapSendGrid

Each row is one setting, with the destination's current value on the left and the incoming value on the right. The arrow points at the destination, and the sentence underneath the picker spells it out: "37 change(s) will be merged from Production into Develop." The confirmation dialog labels the two sides ORIGIN and DESTINATION outright.

The field on the left is the destination — the environment that will be written to. Read the sentence, not the layout, before you apply anything.

Underneath, the diff is exactly two sections — auth and communications — each a list of field, before, after. The console fans them out into readable groups (sign-up, sign-in methods, password, MFA, tokens, social providers, sudo mode, attack protection, OAuth server, rate limits, notifications), but that grouping is presentation. The unit that moves is a settings field.

What a merge does not carry

This is the part worth internalising, because the omissions are deliberate and none of them are errors:

credentials and secretsnever merged

Provider API keys, SMTP passwords, OAuth client secrets. They must be configured by hand in the destination. The console warns about this on the Compare screen for good reason: a promoted provider that is enabled but unconfigured fails at delivery time, not at merge time.

users, sessions, identitiesnever merged

Environments are separate stores. Promotion moves settings, never data.

edge functionsnot environment-scoped

A function belongs to the project, not to an environment, so there is nothing about it in a diff. Its URL is always built from the production slug — acme.nulvless.co/functions/<slug>.

Applying it

Merging is sudo-protected. Confirming opens the access challenge, and the confirmation holds for one hour — so a sequence of promotions asks once, not every time.

A merge cannot be undone. There is no revert, and the destination's previous values are not kept as a restore point — only as the before column of the recorded snapshot. Promote into develop or staging first and read the diff there.

The merge itself runs as a pipeline and produces a deployment:

queuedstatus

Accepted, not started.

runningstatus

Steps in progress.

succeededstatus

Every step finished.

failedstatus

A step failed. The step list shows which.

A run promotes the two sections in order — initialise, then authentication, then communications — and the detail screen times each step individually. A real one completes in about three seconds.

Reading the history

Others → Environments lists every environment and, below them, Environment merges — the history of promotions, filterable by status. Each row opens a detail screen carrying the origin, the destination, the deployment id, who requested it, a timestamped event log, and an Applied changes snapshot: the settings as they were compared when the merge was requested.

That snapshot is the audit trail: the same before-and-after rows you reviewed, frozen at the moment the merge was requested. It is what answers "who changed this, and what was it before" months later, without depending on anyone having written it down.

A merge description is generated for you when the diff is prepared, so the history reads as prose rather than a field count. It is written from the diff at request time — it describes what was promoted, not what the environment looks like now.

A working shape

Promote in one direction and let the diff stay small:

Change settings in develop and exercise them against acme-develop.nulvless.co.

Compare develop into staging. Read the diff — it is the review.

Configure the destination's credentials by hand, once, before the first promotion that needs them.

Promote into main last, when the same configuration has already run somewhere else.

A large diff is not a merge problem; it is a sign that two environments have been edited independently for a while. The cheapest fix is to promote more often.

Next

On this page