Docs/Sandbox ports and params (Beta)
#

Sandbox ports and params (Beta)

Ports and their public URLs, per-sandbox params (port, secret) and env templating in Sandboxes v2.

A Sandboxes v2 sandbox exposes the ports you declare, each on its own URL, and hands your app per-sandbox values (its own URLs, fresh secrets) through params that you map into environment variables with env.

01Ports

yaml
ports:
  web: { port: 3000, access: public }
  api: { port: 3001, access: public }
FieldTypeRequiredMeaning
portinteger, 1–65535YesThe port the app listens on inside the sandbox.
accesspublic | privateYespublic gets a real, routable https URL that a test or a person can reach from outside. private stays internal: reachable by other services in the sandbox, not from outside it.

Port names are lowercase letters, digits and -, starting with a letter, up to 32 characters.

Public URLs

Each public port gets its own https URL, unique per sandbox. Every claim is a fresh copy with its own URLs, and a URL is never reused. cofactor sandbox claim --wait and cofactor sandbox show <id> print them (portUrls in --json).

Why several ports

Bundle web and API in one sandbox, and one claimed sandbox serves both UI flows (driving the browser against web's URL) and API sequences (calling api's URL directly) against the same data. The workspace a UI test just created through the browser is the same row an API test can query. There's no separate "API sandbox" to keep in sync.

Limits and reserved ports

  • At most 8 ports per sandbox. That count includes the implicit emulation port, added the moment any plugin is declared under emulation:, and the implicit object-storage port, added when object_storage is declared. You don't declare either, but both count.
  • emulation and object-storage are reserved port names.
  • With emulation on, ports 80 and 443 are reserved for the emulator: none of your ports can use them.
  • With object storage on, port 9000 (S3), or 4443 and 4444 (GCS), are reserved for the storage server.

cofactor sandbox validate checks all of these.

02Params

Per-sandbox values, filled in by prepare when a sandbox is claimed, and referenced from the top-level env as ${params.name}. Param names are lowercase letters, digits and _, starting with a letter.

ShapeValue in each sandbox
"a string"That fixed value, the same in every sandbox.
{ port: <name> }The sandbox's own public URL for that port.
{ secret: true }A random value (64 hex characters) generated by Cofactor, such as a session-signing key. See Secret params for when it changes.
yaml
params:
  web_url: { port: web }
  api_url: { port: api }
  storage_url: { port: object-storage }   # only with object_storage declared
  session_secret: { secret: true }
  support_email: support@example.test
  • A { port } param must name a declared port, or object-storage when object_storage is declared. Each one resolves to exactly the port it names, so the app is handed its own public origin rather than hardcoding one.
  • Params belong to the definition, not to an environment: they're the same in every environment and can't vary a flag between environments. Use an environment's env: or a seed action for that.

Secret params

A { secret: true } value is generated when a sandbox is first prepared: when a ready snapshot is built, or when a pull request preview is created. Each claim copied from a ready snapshot keeps the snapshot's value (so its services don't need a restart for it), while its port URLs and persona passwords are new. Claims of the same version and environment therefore share a secret value; a new version or environment gets a new one. It never appears in your definition or your repository.

Where params reach

RunsGets
Services with reload_on_prepare: true, prepare commands and actionsThe sandbox's own values, through env. Prepare commands and actions also get every param directly as COFACTOR_PARAM_<NAME> (the name uppercased).
Services without reload_on_prepare, and build steps (GitHub)Layer values: a fixed string as written, a { port } param as http://127.0.0.1:<port>, a { secret } param as a placeholder. These processes start before any sandbox exists, and are never restarted with its values.

So any service that reads a { port } or { secret } param needs reload_on_prepare: true.

03env

Environment variables for the services, prepare commands and actions. ${params.name} anywhere in a value is replaced with that param's value:

yaml
env:
  DATABASE_URL: postgres://app@127.0.0.1:5432/app
  PUBLIC_URL: ${params.web_url}
  API_URL: ${params.api_url}
  SESSION_SECRET: ${params.session_secret}
  CALLBACK_URL: ${params.web_url}/auth/callback
  • Every ${params.name} must name a declared param; validate reports the path of any that doesn't.
  • Substitution happens only in this top-level env. An environment's env: (under environments.<name>.env) is used as written.

OAuth callback URLs

Map the callback to the public port that serves it, for example GOOGLE_REDIRECT_URI: ${params.api_url}/auth/callback. Use your app’s actual variable name and path. With an existing configurable callback, this is sandbox configuration rather than an auth code change. See Google callback configuration for provider emulation and compatibility limits.

Environment env

Each environment can add its own variables, layered on top of the top-level env:

yaml
environments:
  beta-features:
    extends: default
    description: Same as default, with the timeline view turned on.
    env:
      FEATURE_FLAGS: timeline
  • With extends, the parent's env: merges first and the child's last, so a child overrides a single key without repeating the rest.
  • It reaches only services with reload_on_prepare: true, lifecycle.prepare commands, and actions.
  • It does not reach lifecycle.build steps (they run once per commit, before any environment is chosen), nor services without reload_on_prepare. A value your build bakes into its output can't vary by environment: read it at serve time instead, or set it with a seed action.

Order of precedence

For services with reload_on_prepare, prepare commands and actions, each source overrides the one before it:

  1. A pushed image's own ENV.
  2. The object_storage provider's client variables, when declared.
  3. The top-level env, resolved with the sandbox's params.
  4. The claimed environment's env:, parent first.
  5. Emulation settings, when emulation is declared (including NODE_EXTRA_CA_CERTS; see Emulation).

Prepare commands and actions then also get COFACTOR_PARAM_<NAME> and COFACTOR_PERSONAS.