Skip to content
Noite
Esc
↑↓navigate↵open⌘Jpreview
On this page

Environments

Preview environments — one per branch, each on its own host with its own fresh storage.

An environment is a real deployment of one app’s branch on celld: its own fleet, its own host, its own storage, from the same build pipeline as production. main is the app itself, never an environment — an environment deploy never touches the app’s bucket, ports or deploy state.

On the app page’s Deployments tab each environment is its own group, below Production (/apps/<id>?t=deployments&env=<id> deep-links to one): the group header shows its label, what it tracks, status, last deploy, URL and whether it is public, with the environment’s own deploy history beneath it.

Host <label>.<slug>.<base> — e.g. staging.my-app.noite.now
Storage Fresh and empty: s3://<bucket>/envs/<slug>/<label>, so celld creates new D1 databases, Durable Object state and R2 buckets on the environment’s first deploy
Tracks refs/heads/<branch> (a branch environment) or refs/previews/<user>/<branch> (a workspace preview)
Label Derived from the branch: lowercased, separators to -, trimmed to a DNS label (≤ 40 chars), with a suffix on collision. preview and app-0 are reserved
Access Members only by default; a per-environment Public switch

Create one

Create environment at the top of the Deployments tab asks for a branch (the picker lists the app’s branches) and deploys its head. The branch must exist; main is refused. The label is derived from the branch name — there is no free-text label, so a branch named preview gets preview-2 when preview is taken.

Creating an environment counts against the caps in Limits: RUNNER_MAX_ENVIRONMENTS_PER_APP (5) per app and RUNNER_MAX_ENVIRONMENTS (25) per instance. Both are enforced in the runner, so neither the CLI nor an agent can go over them.

Storage starts empty

An environment is its own celld fleet with its own bucket prefix, so its first deploy creates new D1 databases, Durable Object namespaces and R2 buckets, and the app’s migrations run exactly as they do on a first production deploy. Production data can be neither read nor wiped from an environment, and there is deliberately no seeding step: Noite has no D1 dump/restore primitive to copy production into one.

Variables

An environment inherits the app’s variables (Configure) and can override any of them for itself: Variable overrides in the environment’s group adds NAME=value rows that win for that environment alone. Removing an override falls back to the app’s value.

Access

An environment is members only by default. Its sites run an access gate (forward_auth in the generated Caddyfile): a request without the gate’s cookie is redirected to the control UI (/api/env-auth?env=&app=&return=), which checks the session and the app’s view role and mints a short-lived token (60 s). A signed-out visitor lands on the login screen with the environment as its return, so signing in comes straight back. The runner’s edge verifies that token and sets a host-only, HttpOnly, SameSite=Lax __noite_env cookie on the environment’s own host, valid for 12 hours — so signing in to the control UI once is enough, and the cookie never travels to the control UI, to production, or to another environment. The gate reads it and then strips it from the Cookie header, so the environment’s app never sees it.

An admin can flip Public on an environment (e.g. staging) to serve it to anyone with the URL — no gate. The environment site carries Cross-Origin-Resource-Policy: same-site, which is what lets Code mode’s Preview panel frame it. Inside that frame (and only there) the page also gets Cross-Origin-Embedder-Policy: credentialless, because the Code mode page is cross-origin isolated: cross-origin images, scripts and fonts still load, but without cookies. Open the environment in its own tab to see it exactly as visitors do.

Framed loads reach the app as page loads

The Preview panel frames the environment (and production, for the default branch). A browser marks a framed load Sec-Fetch-Dest: iframe (a new tab sends document), and an app that renders only document navigations — oxidejs 0.6’s worker, for one — would answer the frame with its own 404 while the same URL renders fine in a tab. So the edge rewrites Sec-Fetch-Dest to document on the way to the app when the frame is same-site (Sec-Fetch-Site: same-site, which is the control UI framing it); a cross-site page embedding the app still sends its own iframe. Only the request changes: the app’s own X-Frame-Options or CSP frame-ancestors still decide whether the browser shows the frame. Production gets the same framing headers and rewrite, on framed loads only.

Code mode

Code mode’s Preview panel frames what is deployed for the browsed branch and follows it: while the workspace has uncommitted changes it frames the viewer’s own workspace preview (refs/previews/<user>/<branch>, never a branch), otherwise the branch’s environment — production for the default branch — and every push to the branch redeploys its environment, so the frame reloads once that deploy succeeds (until then the previous build is what serves). Deploy preview snapshots the uncommitted changes onto that hidden ref and deploys that environment, leaving the branch and the changes exactly as they were; a preview whose snapshot is older than the latest edit says so, with the deploy beside it.

Lifecycle

  • Scale to zero. Like an app, an environment’s fleet sleeps after RUNNER_SLEEP_AFTER_H hours without requests and wakes on the next one (Limits).
  • Branch deletion deletes its environment — as does Delete in the environment’s group (which also removes its fleet, its storage and its deploys).
  • Workspace previews expire after RUNNER_ENVIRONMENT_TTL_H hours (168 = 7 days) without a deploy.

Each environment host needs its own certificate: the edge mints one on demand, ask-gated to an existing environment of a real app. The caps bound how many certificates one domain can ask for.

CLI and agents

noite environments list --slug my-app
noite environments create --slug my-app --branch feature-x
noite environments public staging on --slug my-app
noite environments deploy staging --slug my-app
noite environments vars set staging API_URL https://api.example.com --slug my-app
noite environments delete staging --slug my-app

The commands are thin clients over the control UI’s MCP endpoint (CLI), so an agent gets the same surface: list_environments, create_environment, delete_environment, deploy_environment, set_environment_public, environment_env_list, environment_env_set and environment_env_unset (Agents).

Limitations

  • No seeding from production. An environment always starts empty; nothing copies production’s D1, R2 or Durable Objects into it.
  • Metrics, errors and the storage browser are app-wide. The telemetry ingest does not read envs/<slug>/<label>/ yet, and the D1/R2/DO browsers read the app’s own fleet, so an environment shows its deploy log and live log only.
  • Builds queue behind production. Every build of an app — production or preview — takes the same per-app lock and the same build cache.

Was this page helpful?