Skip to main content

Environment Variables

These are the main environment variables used by a self-hosted Superposition deployment.

Core Runtime Settings

VariableExampleNotes
PORT8080HTTP listen port.
APP_ENVPROD, DEV, TEST, or SANDBOXSelf-hosted deployments should typically use DEV or PROD. The practical behavior differences for self-hosting are documented below in Secret Loading Modes.
SERVICE_PREFIXempty or spSet empty for root deployment. A value like sp serves the app under /sp.
CAC_HOSThttps://superposition.example.comPublic base URL used by generated links and internal callbacks.
API_HOSTNAMEhttps://superposition.example.comPublic API host exposed to the UI.
SUPERPOSITION_VERSIONv0.112.0Sent in the X-SERVER-VERSION response header.
WORKER_ID1Unique ID generator worker. Use a unique value per replica, from 0 to 255.
RUST_LOGinfo,superposition=infoLogging filter for JSON logs.
ACTIX_WORKER_COUNT5Number of Actix workers.
ACTIX_KEEP_ALIVE120Keep-alive timeout in seconds.
SUPERPOSITION_TOKENsecret valueInternal service token. In DEV, TEST, and SANDBOX, it defaults to 123456 if unset. In PROD, provide a KMS ciphertext.

Database And Cache Settings

VariableExampleNotes
DB_USERsuperpositionPostgreSQL user.
DB_PASSWORDsecret valuePlain value in DEV or TEST. In PROD and SANDBOX, provide a KMS ciphertext.
DB_HOSTpostgres:5432Host and port without scheme.
DB_NAMEsuperpositionPostgreSQL database name.
MAX_DB_CONNECTION_POOL_SIZE10PostgreSQL pool size per app process.
REDIS_URLredis://redis:6379Optional. Leave unset to disable Redis caching.
REDIS_POOL_SIZE10Optional Redis pool size override.
REDIS_MAX_ATTEMPTS10Redis reconnect attempts.
REDIS_CONN_TIMEOUT1000Redis connect timeout in milliseconds.

Kronos Webhook Delivery

Kronos provides durable, retryable webhook delivery. It runs as an embedded library by default; setting KRONOS_URL selects service mode.

VariableModeNotes
SUPERPOSITION_HOSTBothCallback base URL reachable by Kronos. Include SERVICE_PREFIX when configured. Falls back to CAC_HOST.
KRONOS_DISPATCH_TOKENBothSecret used only to authenticate Kronos callbacks.
KRONOS_ENCRYPTION_KEYLibraryStable 64-character hexadecimal key. Generate with openssl rand -hex 32. Do not leave this unset in production (the app defaults to 64 0 characters).
KRONOS_DB_POOL_SIZELibraryEmbedded Kronos PostgreSQL pool size per app process. Defaults to 1.
KRONOS_TABLE_PREFIXLibraryPrefix for embedded Kronos tables. Defaults to kronos_.
KRONOS_URLServiceKronos API base URL without /v1. Its presence enables service mode.
KRONOS_API_KEYServiceAPI key accepted by the Kronos service.
KRONOS_ORG_IDServiceExisting Kronos organisation used by Superposition (must already exist in Kronos). Defaults to superposition.
KRONOS_WORKSPACEServiceShared Kronos workspace. Defaults to superposition.

See Kronos for the mode comparison and setup examples.

Required Validation Flags

These flags are required during startup:

ALLOW_SAME_KEYS_OVERLAPPING_CTX=true
ALLOW_DIFF_KEYS_OVERLAPPING_CTX=true
ALLOW_SAME_KEYS_NON_OVERLAPPING_CTX=true

TENANT_MIDDLEWARE_EXCLUSION_LIST is also required. Start with the default list from .env.example and only change it if you are modifying route prefixes or frontend asset paths:

TENANT_MIDDLEWARE_EXCLUSION_LIST="/health,/assets/favicon.ico,/pkg/frontend.js,/pkg,/pkg/frontend_bg.wasm,/pkg/tailwind.css,/pkg/style.css,/assets,/admin,/oidc/login,/admin/organisations,/organisations,/organisations/switch/{organisation_id},/"

Secret Loading Modes

Superposition currently has two practical deployment modes:

  • APP_ENV=PROD: the app initializes AWS KMS and expects encrypted, base64 KMS ciphertext values for sensitive settings such as DB_PASSWORD, SUPERPOSITION_TOKEN, KRONOS_API_KEY, KRONOS_DISPATCH_TOKEN, OIDC_CLIENT_SECRET, OIDC_INTROSPECTION_AUTH_HEADER and OIDC_API_STATIC_TOKENS (when API-token auth is enabled), CASBIN_DB_PASSWORD, and MASTER_ENCRYPTION_KEY.
  • APP_ENV=DEV: the app reads those values directly from environment variables. This is useful for local and non-AWS self-hosted deployments. If you use this mode outside local development, inject secrets from your platform secret manager and enable real authentication.

If you want to use Superposition secrets, set MASTER_ENCRYPTION_KEY to a base64-encoded 32-byte key:

openssl rand -base64 32

Optional key rotation support:

VariableRequiredNotes
MASTER_ENCRYPTION_KEYYes, for secretsCurrent base64-encoded 32-byte key.
PREVIOUS_MASTER_ENCRYPTION_KEYNoUsed during key rotation fallback.

See Secrets for workspace and master key rotation behavior.

Authentication

For private evaluation, you can disable authentication and authorization:

AUTH_PROVIDER=DISABLED
AUTH_Z_PROVIDER=DISABLED

For production, configure OIDC authentication:

AUTH_PROVIDER=OIDC+https://issuer.example.com/realms/users
OIDC_CLIENT_ID=superposition
OIDC_CLIENT_SECRET='<client-secret-or-kms-ciphertext>'
OIDC_REDIRECT_HOST=https://superposition.example.com

For organisation-scoped OIDC:

AUTH_PROVIDER=OIDC_SAAS+https://issuer.example.com/realms/users
OIDC_CLIENT_ID=superposition
OIDC_CLIENT_SECRET='<client-secret-or-kms-ciphertext>'
OIDC_REDIRECT_HOST=https://superposition.example.com
OIDC_ORG_TOKEN_ENDPOINT_FORMAT='https://issuer.example.com/realms/<organisation>/protocol/openid-connect/token'
OIDC_ORG_ISSUER_ENDPOINT_FORMAT='https://issuer.example.com/realms/<organisation>'

Optional API-token authentication (OIDC only) is enabled with a prefix plus at least one validation mechanism: static tokens and/or RFC 7662 token introspection. When both are configured, a presented key is matched against static tokens first, then introspected.

OIDC_API_TOKEN_PREFIX=apikey                      # arbitrary, operator-chosen; marks a bearer token as an API key
OIDC_API_TOKEN_DELIMITER=_ # optional; separates prefix and key (defaults to "_")

# Mechanism 1 — static tokens. KMS-encrypted (like OIDC_CLIENT_SECRET) JSON array;
# each entry maps a token to a fixed principal. In SaaS, "org" binds a token to an
# organisation; omit "org" for a global-scoped token. "email" is optional.
OIDC_API_STATIC_TOKENS='[{"token":"<key>","principal":"svc-ci","org":"acme"}]'

# Mechanism 2 — RFC 7662 introspection. Verbatim Authorization header sent to the
# endpoint (Bearer or Basic). Secret — KMS-encrypted in non-dev.
OIDC_INTROSPECTION_AUTH_HEADER='Bearer <token>' # or 'Basic <base64(id:secret)>'

# Introspection endpoint URLs are OPTIONAL — when unset, the endpoint is
# discovered from provider metadata. Simple: single endpoint (optional).
OIDC_TOKEN_INTROSPECTION_URL='https://issuer.example.com/realms/users/protocol/openid-connect/token/introspect'

# SaaS OIDC: the per-org format is REQUIRED when introspection is enabled (a
# global-only SaaS introspection config is rejected at startup); the global
# (Login::Global) endpoint is discovered unless OIDC_TOKEN_INTROSPECTION_URL
# overrides it.
OIDC_ORG_TOKEN_INTROSPECTION_URL_FORMAT='https://issuer.example.com/realms/<organisation>/protocol/openid-connect/token/introspect'

Optional cache-tuning knobs (all in seconds). Defaults are sensible; override only if you need to. Values are read at startup, so a malformed value fails the process at boot rather than on a request.

# Cap on how long an RFC 7662 introspection result is cached, regardless of the
# token's own expiry — keeps revocation lag small for long-lived tokens. (300)
OIDC_MAX_INTROSPECTION_CACHE_TTL_SECS=300

# For Basic-auth (password / client_credentials) token-exchange caching:
# how far before the exchanged token's expiry a cached principal is dropped,
# to stay ahead of clock skew and in-flight latency. (30)
OIDC_CACHE_REFRESH_SAFETY_MARGIN_SECS=30

# Fallback cache TTL used when the IdP omits `expires_in` from a Basic-auth
# token response (`expires_in` is only RECOMMENDED by RFC 6749, not required). (60)
OIDC_FALLBACK_TTL_SECS=60

For the complete authentication picture — all credential schemes (including Basic machine-to-machine access and API-key introspection), how they behave, caching, and error semantics — see the dedicated Authentication page.

Authorization

To enable Casbin authorization policies:

AUTH_Z_PROVIDER=CASBIN
CASBIN_DB_USER=superposition
CASBIN_DB_PASSWORD='<database-password-or-kms-ciphertext>'
CASBIN_DB_HOST='postgres.example.com:5432'
CASBIN_DB_NAME=superposition
CASBIN_DB_POOL_SIZE=3
CASBIN_POLICY_REFRESH_INTERVAL=20
ROOT_ADMIN_EMAIL=admin@example.com

Initialize the Casbin policy table once:

export CASBIN_DATABASE_URL="postgres://${CASBIN_DB_USER}:${CASBIN_DB_PASSWORD}@${CASBIN_DB_HOST}/${CASBIN_DB_NAME}"
psql "$CASBIN_DATABASE_URL" -f crates/service_utils/src/middlewares/auth_z/casbin/casbin.sql