Configuration reference
Every OrkStage environment variable with its type, default and effect, from core settings and OIDC to the OpenTofu runner, email, secrets and licensing.
The API and the worker read the same typed configuration (packages/runtime/src/config.ts). They
refuse to start on invalid values and list every problem they find. In local development they load
a .env file from the repository root (cp .env.example .env). Real environments inject the
variables.
Booleans accept true, false, 1 or 0.
Core
| Variable | Type | Default | Description |
|---|---|---|---|
NODE_ENV | development | test | production | development | production turns on Secure cookies and the production safety checks. |
DATABASE_URL | URL | required | Postgres connection string, for example postgresql://platform:platform@localhost:5432/platform. |
REDIS_URL | URL | required | Redis for queues and concurrency slots, for example redis://localhost:6379. |
API_PORT | integer | 4000 | Port the API listens on. |
API_PUBLIC_URL | URL | http://localhost:4000 | Public URL of the API. |
WEB_PUBLIC_URL | URL | http://localhost:3000 | Public URL of the console. OIDC redirect URIs are built from it. |
SESSION_SECRET | string, at least 32 characters | required | Signs the session cookie and the OIDC flow cookie. In production it must not start with change-me. |
LOG_LEVEL | trace, debug, info, warn, error or fatal | info | Pino log level. Tokens, secrets, cookies and authorization headers are always redacted. |
TRUST_PROXY_HOPS | integer, 0 to 5 | 1 | Reverse proxies in front of the API that add the client address to X-Forwarded-For. The console's /api proxy counts as one. The client address (rate limits, audit) is read that many hops back, so clients cannot set it. See rate limits behind proxies. |
API_DOCS_ENABLED | boolean | true, or false when NODE_ENV=production | Serves the OpenAPI document and Swagger UI at /api/docs. |
Authentication
| Variable | Type | Default | Description |
|---|---|---|---|
AUTH_DEV_LOGIN_ENABLED | boolean | false | Enables POST /auth/dev-login, which signs in with an email address only. Startup fails if it is true while NODE_ENV=production. |
OIDC_<KEY>_ISSUER | URL | Issuer of an OIDC provider. <KEY> is any name in uppercase letters and digits. | |
OIDC_<KEY>_CLIENT_ID | string | Client id registered at that provider. | |
OIDC_<KEY>_CLIENT_SECRET | string | Client secret for that client id. | |
SESSION_IDLE_MINUTES | integer, 5 to 1440 | 120 | A session unused for this long ends, even before its absolute limit of 12 hours. |
Single sign on (OIDC)
A provider is enabled when all three OIDC_<KEY>_* variables are set and the issuer contains no
<placeholder>. The keys ENTRA, GOOGLE and GITLAB get display names. Any other key is shown as
written. Register this redirect URI at the provider:
<WEB_PUBLIC_URL>/api/v1/auth/oidc/<key>/callback # key in lowercase, e.g. /oidc/entra/callbackOIDC_ENTRA_ISSUER=https://login.microsoftonline.com/<tenant-id>/v2.0
OIDC_ENTRA_CLIENT_ID=<application id>
OIDC_ENTRA_CLIENT_SECRET=<client secret>The flow uses the authorization code grant with PKCE (S256) and verifies state and nonce. The ID
token's signature is checked against the keys the provider publishes (its JWKS), not only the TLS of
the exchange. Accounts are linked only through an email address the provider has verified.
Microsoft Entra ID is the reference provider. Google and GitLab work through configuration alone but have had less testing.
GitHub sign in
GitHub has no OpenID Connect for people, so it has an OAuth 2 flow of its own with the same state
and PKCE checks. Create an OAuth app (GitHub, Settings, Developer settings, OAuth Apps) with the
callback <WEB_PUBLIC_URL>/api/v1/auth/oidc/github/callback, then:
OAUTH_GITHUB_CLIENT_ID=<client id>
OAUTH_GITHUB_CLIENT_SECRET=<client secret>
# GitHub Enterprise Server only:
OAUTH_GITHUB_URL=https://github.example.comOrkStage asks for read:user and user:email, reads the person's id and emails once, and discards
the access token. Only the primary, verified email can link to an account; the GitHub user id
is the stable link afterwards, so a later email change at GitHub does not move the account.
Account linking
The same rules apply to every provider:
- A person who signed in with this provider before gets the same account (by the provider's id, never by email).
- Otherwise, a verified email links to the existing account with that email.
- Otherwise, pending invitations for that email create the account with their memberships.
- Otherwise, when exactly one tenant lists the email's domain in Allowed email domains, the person joins it as a viewer.
- Otherwise, there is no account: unknown people never get one by default.
Disabled accounts cannot sign in. Each person can see where they are signed in and sign other devices out under Your sessions in the account menu.
Worker and OpenTofu runner
| Variable | Type | Default | Description |
|---|---|---|---|
WORKER_CONCURRENCY | integer from 1 to 64 | 4 | Deployment jobs one worker processes in parallel. |
DEPLOY_LIMIT_PER_TENANT | integer, 0 for no limit | 5 | Deployments of one tenant running at once, across all workers. Others wait in the queue. |
DEPLOY_LIMIT_PER_PROVIDER_REGION | integer, 0 for no limit | 20 | Deployments touching one provider region at once, across all workers. Each cloud target also has its own limit. |
HEALTH_CHECK_INTERVAL_SECONDS | integer, at least 10 | 30 | Interval between health monitor ticks. |
DRIFT_CHECK_INTERVAL_MINUTES | integer, at least 0 | 360 | Interval between drift scans on real providers (one OpenTofu plan per applied resource). 0 turns scans off. |
DNS_RESOLVERS | comma separated addresses | system resolver | DNS servers the worker asks when it verifies custom domains, for example 1.1.1.1,8.8.8.8. Set it when the system resolver is private and could hide the records customers create. |
TOFU_IMAGE | string | ghcr.io/opentofu/opentofu:1.12.7 | Image for runner containers. |
TOFU_MODULES_DIR | path | Module library on the worker host, mounted read only into runners. | |
TOFU_MODULES_IN_IMAGE | boolean | false | The modules are baked into TOFU_IMAGE, so nothing is mounted. |
TOFU_WORK_DIR | path | Where the worker writes the working directory of each resource. | |
TOFU_WORK_VOLUME | string | For a worker that runs in a container: a named volume mounted at TOFU_WORK_DIR and passed on to runners. | |
TOFU_PLUGIN_CACHE_VOLUME | string | orkstage-tofu-plugins | Named volume that runners share as the provider plugin cache. |
TOFU_DOCKER_NETWORK | string | Docker network for runner containers, so they can reach an emulator by name. | |
TOFU_TIMEOUT_MINUTES | integer from 1 to 240 | 45 | Hard time limit for one OpenTofu command. |
AWS_ROLE_SESSION_MINUTES | integer from 15 to 720 | 60 | Length of the AWS sessions OrkStage requests when it assumes a deployment role. Each OpenTofu run gets credentials that outlive TOFU_TIMEOUT_MINUTES, so keep this above it; above 60, raise the role's MaxSessionDuration too. |
AWS_EMULATOR_ENDPOINT | URL | Emulator endpoint as seen from inside containers, for example http://ministack:4566. |
| Variable | Type | Default | Description |
|---|---|---|---|
SMTP_HOST | string | localhost | SMTP server. |
SMTP_PORT | integer | 1025 | SMTP port (Mailpit in local development). |
SMTP_SECURE | boolean | false | Use TLS from the start of the connection. |
SMTP_USER, SMTP_PASSWORD | string | none | Credentials for mail servers that require them. |
EMAIL_FROM | string | OrkStage <noreply@example.local> | Sender address. |
EMAIL_TRANSPORT | smtp or ses | smtp | ses sends through Amazon SES v2 with the worker's own AWS credentials and AWS_REGION; EMAIL_FROM must be a verified identity. |
EMAIL_RATE_LIMIT_PER_HOUR | integer from 1 to 10000 | 30 | Notification emails one person receives per hour at most; the rest stay in the console. |
Secrets and integrations
| Variable | Type | Default | Description |
|---|---|---|---|
SECRETS_AWS_REGION | string | us-east-1 | Default region for aws-sm:// references given by name. ARNs carry their own region. |
SECRETS_AWS_ENDPOINT | URL | Secrets Manager endpoint override for emulators, as seen from the API and the worker. | |
ENCRYPTION_KEY | base64, 32 bytes | Encrypts secrets the platform must read back, such as webhook signing secrets. Required in production. Generate it with openssl rand -base64 32. | |
WEBHOOK_ALLOW_PRIVATE | boolean | false | Allows http:// webhook URLs and URLs on private networks. For local development only; refused in production. |
env:// references cannot read the platform's own configuration (DATABASE_URL, REDIS_URL,
SESSION_SECRET, ENCRYPTION_KEY, ORKSTAGE_LICENSE_KEY and anything starting with OIDC_,
SMTP_, AWS_, TOFU_, API_, WEB_, APP_ or NODE_). Give target credentials their own
names, for example ORK_AZURE_CREDENTIALS.
See Secret references.
Rate limits behind proxies
Rate limit counters live in Redis, so they hold across every API instance. Signed in requests are
counted per session or API key. Sign in routes are counted per client address, which is only as
trustworthy as the proxy that records it: put a load balancer or reverse proxy that appends the
client address to X-Forwarded-For (an AWS ALB, nginx with proxy_add_x_forwarded_for) in front
of the console, and leave TRUST_PROXY_HOPS at 1. The console's own proxy passes an incoming
X-Forwarded-For through unchanged, so without such a proxy a client can pick the address the API
sees for sign in limits. If the API is exposed directly, set TRUST_PROXY_HOPS=0.
Licensing and observability
| Variable | Type | Default | Description |
|---|---|---|---|
ORKSTAGE_LICENSE_KEY | string | Enterprise license key (it starts with orkstage-license.v1.). Empty means Community edition. | |
OTEL_EXPORTER_OTLP_ENDPOINT | URL | OpenTelemetry export (OTLP over HTTP) of traces and metrics from the API and the worker. Empty: off. See Observability. |
Console
| Variable | Default | Description |
|---|---|---|
API_INTERNAL_URL | http://localhost:4000 | API origin (no path) that the console forwards /api/* to, for example http://api:4000. The console resolves its rewrites when it is built, so set this variable at build time. |
Seed
The seed runs after migrations (the migrate service, or pnpm db:seed). It is idempotent.
| Variable | Default | Description |
|---|---|---|
PLATFORM_OWNER_EMAIL | none | Creates this user as the platform owner, or gives an existing user that role. There is no development sign in in production, so this is how the first administrator is created: they sign in with SSO using this email, which the provider must report as verified. Without it, a production seed warns that nobody can administer the platform. |
ORGANIZATION_NAME | Organization | Name of the organization created on the first run, when none exists. |
SEED_DEMO_DATA | true, except with NODE_ENV=production | Adds the demo: simulated cloud targets, sample images, the Acme and Globex tenants and their users. Without it, the seed writes only plans, built in and marketplace templates, the organization and the platform owner. |
Production safety checks
With NODE_ENV=production, startup fails when any of these is true:
AUTH_DEV_LOGIN_ENABLEDistrue.ENCRYPTION_KEYis missing.WEBHOOK_ALLOW_PRIVATEistrue.SESSION_SECRETstill starts withchange-me.
Docker Compose deployment
Profiles, services, volumes and networks of the OrkStage Compose stack, how the worker starts OpenTofu runners, and what to change before you share it.
Connecting an AWS account
Create the OrkStage deployment role with an external id, prepare the OpenTofu state bucket, then register and validate an AWS account as a cloud target.