w6w docs

Self-host configuration reference

Generated from the host’s own configuration module — the generator refuses to write a page that disagrees with what config.ts currently reads. See Install for the compose bundle that sets the handful of these you must supply yourself (it also passes the optional ones through .env; .env.example lists them), and Troubleshooting for what happens when one of these is wrong.

Operator login and sessions

VariableDefaultWhat it does
AUTH_USERNAMEadminThe operator login used to sign in and manage the app registry.
AUTH_PASSWORDadminThe operator’s password. Outside development mode the server refuses to start if this is unset or left at the default.
JWT_SECRETa dev default — never use it in productionSigns the session tokens this host mints. Must differ from OPS_JWT_SECRET, or the server refuses to start.
AUTH_TOKEN_TTL_SEC86400How long a signed-in session stays valid.
AUTH_EXCHANGE_TTL_SEC900Lifetime of a token minted for one of your own end users through the token-exchange endpoint.
AUTH_IMPERSONATION_TTL_SECsame as AUTH_EXCHANGE_TTL_SECLifetime of an operator impersonation session.
TENANT_SECRET_ROTATION_OVERLAP_SEC86400How long a just-rotated tenant client secret keeps verifying, so an in-flight rotation never breaks a caller mid-request.
SIGNUP_MODESelf-host: open when unset or blank. Cloud: invite-only.Whether an uninvited visitor can self-serve sign up. The exact value open allows it, and so does an unset or blank value on a self-host install; any other value (including Open) keeps signup invite-only, and so does unset on cloud. invite_only closes registration after setup, and exposing the port with open signup lets any visitor register.
SIGNUP_TENANTw6wWhich tenant a fresh signup, or a request that names none, lands in.
INVITE_TTL_SEC86400How long an account invite stays valid.
OPS_JWT_SECRETunsetSigns the apps:publish ops tokens for the two artifact-publish routes (GET /system-ops/apps/digests, POST /system-ops/apps/artifacts) this host serves. Unset, those routes answer 401 to everyone. Must differ from JWT_SECRET, or the server refuses to start.
W6W_DEV_MODEoffA local-development escape hatch: while on, boot safety no longer refuses to start on a default/insecure AUTH_PASSWORD. Leave it unset (off) on any real deployment.
W6W_OPERATOR_SESSION_TTL8hHow long an operator session stays valid. Either a bare number of seconds or a number with an s, m, h or d suffix (for example 30m, 8h, 7d), resolving to between one minute and 30 days; anything else — a decimal, a negative number, a unit this page does not list — refuses to start. Signed-in operators can list their own sessions and revoke one.
W6W_OPERATOR_OIDC_ISSUERunset — Operator SSO offThe OpenID Connect issuer your operators sign in through. Unset or blank means operator SSO does not exist on this install at all — the login form is the only way in. Once set, it must be a valid URL using https: (http: is accepted only while W6W_DEV_MODE is on) or the server refuses to start; the rest of the W6W_OPERATOR_OIDC_* variables below then become required.
W6W_OPERATOR_OIDC_CLIENT_IDnone — required once the issuer is setThe OIDC client id this install signs in as. Required as soon as W6W_OPERATOR_OIDC_ISSUER is set: an empty value refuses to start rather than leaving you with a login page that cannot work.
W6W_OPERATOR_OIDC_CLIENT_SECRETunset — a public clientThe OIDC client secret. Leaving it unset or empty runs this install as a public client: the authorization-code flow with PKCE and no secret, which is what most identity providers expect from a self-hosted install. Set it only when your provider requires a confidential client.
W6W_OPERATOR_OIDC_OPERATOR_GROUPSnone — required once the issuer is setWhich of the issuer’s groups become which operator scopes, written as the group-to-scope mappings this host understands. Required as soon as W6W_OPERATOR_OIDC_ISSUER is set, and at least one mapping must resolve; an empty value or a malformed list refuses to start.
W6W_OPERATOR_OIDC_GROUP_CLAIMgroupsWhich claim in the issuer’s token carries the group list that W6W_OPERATOR_OIDC_OPERATOR_GROUPS is matched against. Unset or blank means the claim named groups.

Process and storage

VariableDefaultWhat it does
DATABASE_URLnone — requiredThe Postgres connection string this host connects with. Boot refuses immediately if it is unset.
API_PORT8787The port the server listens on.
APPS_STORAGE_DIR./storage/appsWhere app and asset files are read from. A published self-host image points this at its own baked-in app catalog.
W6W_BLOB_DRIVERfsWhere content-addressed blobs are stored: fs (a directory on this host) or gcs (a Google Cloud Storage bucket). Exactly those two values, lowercase; anything else refuses to start.
W6W_BLOB_DIR./storage/blobsThe directory the fs blob driver writes into, created if missing. Boot refuses if it cannot be written, and warns when it sits on memory-backed storage (tmpfs), where blobs would not survive a restart. Mount persistent storage here. Ignored by the gcs driver. A spoke process reads the same variable for its own local directory of built-app code fetched from the hub; that directory is write-once and never pruned.
W6W_BLOB_GCS_BUCKETnone — required when the driver is gcsThe name of the GCS bucket the gcs blob driver uses. A missing or malformed bucket name refuses to start. Ignored by the fs driver.
W6W_EXEC_CACHE_BYTES67108864 (64 MiB)Byte budget of the in-process cache of built-app code (app.js) loaded from the blob store, evicted least-recently-used. A single app larger than the budget still runs, it is just not kept. A positive whole number of bytes; zero, a negative, a fraction, a unit suffix (64MB) or exponent notation (1e6) refuses to start. Unset or blank uses the default. A spoke process reads this variable too, with the same rules, for its own cache.

Public URLs and Studio

VariableDefaultWhat it does
PUBLIC_BASE_URLthe local API addressThe public URL at which the API’s root is reachable, including any proxy path prefix (the self-host bundle sets https://<domain>/api). An exposed Endpoint’s callable URL is rendered against it, so set this to your real origin or operators will see localhost URLs.
WEBHOOK_BASE_URL<PUBLIC_BASE_URL>/triggers/webhooks/{id}, else the local API address (the self-host bundle derives https://<domain>/api/triggers/webhooks/{id})URL template inbound webhook triggers are built from, with an {id} placeholder. The self-host bundle derives https://<domain>/api/triggers/webhooks/{id} from your domain unless you override it. An app registers this URL with its vendor, so it must be reachable from the internet.
STUDIO_BASE_URLhttp://localhost:5173Studio’s own origin, used to build links such as invite redemption.
SERVE_STUDIOoffServe a built Studio bundle from this same process at /, instead of running Studio as its own deployment.
STUDIO_DIST_DIR../studio/dist in the images; ../studio/packages/studio/dist when that directory exists (a source checkout)Where the built Studio bundle lives. Only read when SERVE_STUDIO is on, or by the studio-only container mode.

Scheduling and execution

VariableDefaultWhat it does
SCHEDULER_ENABLEDonWhether this replica runs the in-process cron scheduler. Every scheduled job is a database-wide advisory-lock singleton, so more than one replica sharing a database never double-fires regardless of this flag.
RUN_CONCURRENCY_PER_REPLICA5How many workflow runs this one replica executes at once. A per-replica budget, separate from the licence’s install-wide parallel-execution limit; in a container, size it against the node’s cpuLimit (its cgroup CPU ceiling), never against cpus — see Nodes and the resource scan.
TRIGGER_MIN_INTERVAL_MS300000 (5 minutes)The shortest interval a poll trigger subscription may check at, in milliseconds. A whole number of at least 60000 (1 minute); blank or unset means 5 minutes, and anything else refuses to start. A trigger may declare a longer minimum of its own, and the longer of the two applies. Read when a subscription is created, so a change affects new subscriptions only.
REPLICA_IDgeneratedThis replica’s single identity: the node id it registers under AND the id its claims, logs and admission counters carry. Set it whenever you run more than one replica: it must be stable across restarts and unique per replica. Unset, every boot invents a fresh random id, leaving a gone node row behind (hidden from GET /nodes unless ?include=gone, not pruned yet) and a dead admission-counter entry (never cleaned up); two live replicas sharing one id also read and reset each other’s counts.

Nodes, labels and resource inventory

VariableDefaultWhat it does
W6W_NODE_ROLEallWhat this process is for. all serves the API and executes runs; api serves the API and executes none; executor executes runs and serves no API. Blank or unset means all, so a deployment that configures nothing boots exactly as before; any other value refuses to start, and API is a mistake rather than a synonym. The value spoke starts a spoke instead of a server — see Hub and spokes.
W6W_DRAIN_TIMEOUT_SEC60How long a shutting-down node waits for the runs it is executing. A whole number of seconds from 0 to 3600; 0 means do not wait at all; a decimal, a negative number or anything above an hour refuses to start. A run still in flight when the deadline passes is abandoned to the queue — another node picks it up — rather than delaying the shutdown any further.
W6W_NODE_LABELSunsetComma-separated labels this replica publishes about itself, e.g. gpu,zone:lab-2. Recorded with the node, shown on its row in Studio, and matched against a workflow’s x-w6w-placement labels when a run is claimed. Each entry may be 1–64 characters of letters, digits and : . _ / -; one that is not is dropped with a start-time warning rather than refusing boot. See Nodes and the resource scan.
W6W_RESOURCE_MONITORonWhether this node samples its own CPU, memory and load for the server-resources view. on/true/1 (or unset) enables it; off/false/0 disables it. Anything else refuses to start.
W6W_RESOURCE_SAMPLE_INTERVAL_SEC5How often the resource monitor samples. A whole number of seconds from 1 to 60; anything else refuses to start.
W6W_RESOURCE_RETENTION_SEC300How long samples are kept in memory. A whole number of seconds from 10 to 3600, and not less than the sample interval; anything else refuses to start.
W6W_RESOURCE_VISIBILITYeveryone on self-hostWho may read the server-resources view. operators limits it to operator callers; everyone opens it to any signed-in user. Case-insensitive; anything else refuses to start.
W6W_REPORT_INVENTORYoffSelf-host only. When explicitly on (1/true/yes/on), each usage report additionally carries four integers and nothing else — node count, CPUs, GPUs and memory GiB. Hostnames, GPU models, disk paths and labels never leave the host, and a cloud installation never reports inventory whatever this is set to. See Nodes and the resource scan.

Hub and spokes

VariableDefaultWhat it does
W6W_SPOKE_CREDENTIAL_MODEhandoffHub only. How a hub gives a spoke the credentials a leased step needs. handoff (the default) hands each step’s credential to the spoke when that step runs, scoped to the step and recorded in the audit log. proxy hands the spoke nothing: it sends the unsigned request intent and the hub runs the signing and the egress itself. Any other value refuses to start. See Hub and spokes.

System mail (optional)

VariableDefaultWhat it does
MAIL_FROMunsetEnvelope From address for the one system-sent email (an invite notification). Leaving this or POSTMARK_SERVER_TOKEN unset means no system mail is configured; the server still boots.
POSTMARK_SERVER_TOKENunsetPostmark API token for the one system-sent email. Paired with MAIL_FROM above.

Tenancy

VariableDefaultWhat it does
TENANT_DOMAIN_SUFFIXunsetA DNS zone used to suggest a per-tenant domain. Unset means the suggestion route does not exist at all; serving a tenant’s own domain still needs your own DNS, TLS and ingress either way.
SYSTEM_DOCUMENTS_PROJECTunsetOverrides which project the host’s built-in system documents (such as the one-time login code email template) are resolved from. Leave it unset for the default lookup.
VariableDefaultWhat it does
W6W_INSTALLATION_MODEset by the entrypointWhich profile this process boots as. A published self-host image sets this itself; leave it unset, since a contradicting value refuses to boot.
W6W_EDGESthe fixed self-host setWhich API surfaces this process serves. A self-host install always serves exactly the same three; leave it unset.
W6W_USAGE_REPORTINGon once linked or licensed, otherwise offHourly usage reporting. A cloud installation always reports. Self-host defaults to on once the install is linked or licensed through the Warden, and off otherwise. 0/false/off/no (case-insensitively) opts out; 1/true/yes/on opts in. Blank is the same as unset.
W6W_USAGE_REPORT_INTERVAL_MINUTESunset — the governing lease’s own cadence, or 720 (twice a day) with noneSelf-host only (a cloud installation always reports live and never reads this). May only shorten the base cadence, never lengthen it: 0 goes live; 1–4 floors to 5; a value above the base is clamped back to the base with a boot-time warning; anything unparsable is ignored with a warning. Never refuses to boot.

App catalog

VariableDefaultWhat it does
W6W_IMPORT_PACKunsetSet to the exact value official to import the baked-in first-party app pack automatically at boot. Otherwise import it later, on demand.

Usage metering

VariableDefaultWhat it does
USAGE_METERING_ENABLEDoffWhether this host records usage events at all. Self-host defaults off (opt-in); only 1/true/yes/on (case-insensitively) turns it on. The four kind toggles below only matter once this is on.
USAGE_METER_API_CALLonWhen metering is on, whether an inbound API call counts as a usage event.
USAGE_METER_EGRESSonWhen metering is on, whether an outbound egress call counts as a usage event.
USAGE_METER_ACTION_INVOKEonWhen metering is on, whether an action invocation counts as a usage event.
USAGE_METER_WORKFLOW_RUNonWhen metering is on, whether a workflow run counts as a usage event.

Data retention

VariableDefaultWhat it does
W6W_RETENTION_DAYS_FLOOR30The minimum age, in days, before old history is pruned — the window used when no licence supplies its own retention policy. Unset (or blank) means 30; any other value must be a whole number of days of at least 1, or the server refuses to start naming this variable.

Metrics

VariableDefaultWhat it does
W6W_METRICS_ENABLEDonServes GET /metrics on the API port — read-only Prometheus-format metrics, the same figures a licence and a run queue expose elsewhere. Only false/0/off/no turns it off, and then the route does not exist at all: /metrics answers exactly like any other unknown path.
W6W_METRICS_ALLOW_CIDRSloopback only — 127.0.0.0/8, ::1/128The CIDR blocks allowed to read /metrics without a token. A request only qualifies when it reaches the server directly: one carrying X-Forwarded-For or Forwarded never does, so a scrape through a proxy needs an operator token instead. Widen this list only when the server is not reached through something that rewrites client addresses — behind docker run -p or a Kubernetes Service every external caller arrives from a private gateway address, so widening it exposes /metrics. A malformed entry refuses to start.

Nodes and the resource scan

A node is one running host process — one container. Every node registers itself against this installation’s database at start, heartbeats every 10 seconds, and is reported live (seen within 30 s), stale (within 10 minutes) or gone (older than that) — all three derived from the node’s own last-seen stamp, never stored. REPLICA_ID is the node’s id, which is why its stability matters: a stable value across restarts keeps one row and updates it, while a random one leaves a gone row behind per restart. Those rows are hidden, not pruned — GET /nodes leaves them out unless you pass ?include=gone, and pruning arrives with a later project.

Operators reach the roster two ways: Studio → Settings → Installation → Nodes (one row per node, with its own fields) or the operator-only GET /nodes. /health/ready carries only the counts — checks.nodes is {ok: true, total, stale} and never a per-node field.

Each node scans its own machine — at boot, then every 60 seconds for the cheap fields and every 10 minutes for the GPU probe. Every probe is feature-detected, time-boxed and never fatal: a field the node cannot read is reported as null, never as a zero and never as a crash.

FieldSourceWhat it means
cpusnavigator.hardwareConcurrencyThe host’s core count — the wrong number inside a container, where the process sees the machine rather than its own limit.
cpuLimitcgroup v2 cpu.max, or v1 cpu.cfs_quota_us/periodThe cgroup ceiling on how much CPU this node may actually use — the number that bounds it in a container.
memoryTotal, memoryAvailableDeno.systemMemoryInfo()Host memory, with the same container caveat.
memoryLimitcgroup v2 memory.max, or v1 memory.limit_in_bytesThe cgroup memory ceiling.
load1 / load5 / load15Deno.loadavg()The 1-, 5- and 15-minute load averages, in that order.
disk[]df -kP on the apps directory, W6W_CACHE and the runtime’s own DENO_DIRFree space per storage role (apps, cache, deno) — a role name, never the path, so a database dump cannot leak your layout.
gpus[]nvidia-smi (or /proc/driver/nvidia/...), rocm-smi plus /sys/class/drm, system_profiler on macOSVendor and model per card, plus memory and driver when the vendor’s tooling answers. [] means “probed, none found”; null means the probe could not run.
os / arch / deno / containerDeno.build, Deno.version, /.dockerenvWhere the node runs, and whether that is a container.

Sizing a replica. Keep RUN_CONCURRENCY_PER_REPLICA proportional to a node’s cpuLimit — its cgroup ceiling — and not to cpus, which inside a container is the host’s core count and can be an order of magnitude too large. The four inventory counters behind W6W_REPORT_INVENTORY follow the same rule: the CPUs they total are each node’s cgroup limit where one is known.

Labels. W6W_NODE_LABELS publishes your own vocabulary (gpu, zone:lab-2); the scan adds the capabilities it can prove (gpu:nvidia, arch:x86_64, os:linux). Both are recorded and shown on the node’s row, and both place work: a workflow’s x-w6w-placement annotation names labels, and a run of it is claimed only by a node carrying every one of them — while no live node does, the run waits and reports waiting_for_capacity.

GPUs are inventory only — w6w does not run work on GPUs. The registry records what the scan finds so you can plan capacity, and the four inventory counters include GPUs when you opt into reporting them, but the runtime is a Deno Worker: nothing in this build schedules onto a GPU. A gpu label can place a run on a GPU node, but everything a GPU node runs, it runs on its CPUs.

Required variables not read through config.ts

The list above is generated by scanning config.ts itself, so it can only ever cover what that one module reads. A small number of variables are read entirely outside it, by other parts of the server — this generator cannot mechanically prove this section complete, so it is hand-maintained instead.

VariableWhat it does
W6W_CREDENTIAL_KEY64 hex chars (32 bytes). Encrypts stored connection credentials and vault secrets at rest. Unset or malformed falls back to a well-known development key with a loud warning — never rely on that outside development.
W6W_CREDENTIAL_KEY_NEXTOptional. 64 hex chars (32 bytes), different from W6W_CREDENTIAL_KEY. Set it only while rotating the credential key, on every replica, exactly as the rotation runbook in Upgrade describes — the server then reads both keys, and a value that is not 64 hex characters, or that decodes to the same bytes as W6W_CREDENTIAL_KEY, refuses to start. Unset in normal operation.
GOOGLE_APPLICATION_CREDENTIALSOptional. Path to a Google service-account key JSON file. Read only when W6W_BLOB_DRIVER=gcs and the server runs outside Google Cloud: the blob store signs a token request with that key. On Cloud Run (or any host with a metadata server) leave it unset and the runtime service account’s token is used instead. A missing or malformed file refuses to start.
W6W_HUB_URLSpoke only. The hub this spoke is enrolled to, as the base URL a browser would reach it at — including any reverse-proxy path prefix (https://hub.example.com/api on the published self-host bundle). It must use https: with no userinfo, query string or fragment; plain http: is accepted only while W6W_DEV_MODE is on, and only for development. Every call the spoke makes is signed over this URL’s own /hub/... path and never over the prefix, so the same hub can be reached through your proxy. A spoke only ever reaches its hub, on your local network — cross-site spokes over the public internet are not supported in this release.
W6W_HUB_ENROLL_TOKENSpoke only. The one-time token that enrolls this spoke with its hub on its first start. An operator mints it in Studio under Settings → Installation → Nodes → Enroll a spoke, or through POST /nodes/enroll-tokens with an operator token holding operator:installation — the response shows the token once. It is single-use and valid for 15 minutes; the spoke exchanges it for its own key so that later starts need no token, and you can remove it from the spoke’s environment after enrolling.
W6W_SPOKE_KEY_FILESpoke only. Where the spoke keeps its own key, default ./spoke-key.json. The spoke mints the key on first enrolment and refuses to start if the file is readable by its group or by other users — mode 0600, owner-only — and the spoke’s own id lives inside that file, so keep the file on persistent storage and never share it between spokes. A read-only mounted secret works too, as long as it already holds an enrolled key file.
W6W_NODE_LABELSSpoke only. The same variable the node section above documents, read by a spoke with the same parser: on a spoke this is read by the spoke’s own configuration module rather than config.ts, which is why it is listed here as well. A spoke publishes these labels with its enrolment and every heartbeat, and is offered only runs whose placement names labels it reports.
RUN_CONCURRENCY_PER_REPLICASpoke only. The same variable the scheduling section above documents, read by a spoke with the same parser: on a spoke this is read by the spoke’s own configuration module rather than config.ts, which is why it is listed here as well. It is how many runs this spoke executes at once — unset means 5, and anything but a whole number of at least 1 refuses to start.