F FourIA GitHub ↗

FourIA — Environment Variables Reference

On this page

Complete reference of all environment variables used across FourIA Workers. Derived from apps/fouria/src/gateway/env.ts and apps/fouria/.dev.vars.example.


Variable Classification

FourIA uses three classifications for configuration values:

ClassificationStorageBinding TypeManaged ByExamples
Instance SecretCF encrypted secret_textsecret_textlerma provisionerGateway token, CDP secret, AI keys, Stripe
Variablewrangler.jsonc / WFP plain_textplain_textProvisioner / IaCDEV_MODE, URLs, account IDs, sandbox config
Dynamic SecretF4E1-encrypted R2 object in BACKUP_BUCKETEnd-user via integrations UIChannel tokens, OAuth, custom integrations

Instance Secrets

These are automatically generated and deployed by the lerma provisioner as secret_text bindings on each tenant User Worker. For the full inventory of required secrets, see the secrets manifest.

SecretDescription
MOLTBOT_GATEWAY_TOKENAuto-generated gateway token for CLI auth. Mapped to OPENCLAW_GATEWAY_TOKEN in container.
CF_ACCESS_PLATFORM_AUDPlatform-level Access AUD (always present, from dispatch worker).
CF_ACCESS_CLIENT_AUDPer-tenant Access AUD (optional, provisioned by lerma).

Secrets reference: See docs/schemas/secrets-manifest.schema.json for the canonical credential inventory.

Variables (Non-Secret Config)

Operational configuration, safe to store in wrangler.jsonc or .dev.vars. Deployed as plain_text bindings by the provisioner.

VariableContainerRequiredDescription
DEV_MODEYes†NoSkips CF Access auth + bypasses device pairing. Local dev only.
E2E_TEST_MODEYes‡NoSkips CF Access auth but keeps device pairing. For automated E2E.
DEBUG_ROUTESNoNoEnables /debug/* endpoints (ROOT role only). Off in production.
DEMO_MODENoNoEnables demo-specific UI behaviors (read-only, sample data).
SANDBOX_SLEEP_AFTERNoNoContainer sleep timeout: never (default) or 10m, 1h, etc.
SANDBOX_INSTANCE_TIMEOUT_MSNoNoBound for container VM provisioning (default: 20000). Fail-fast detection of a degraded/off container.
SANDBOX_PORT_TIMEOUT_MSNoNoBound for container port readiness (default: 90000). Keep generous — gateway cold start is slow.
SANDBOX_POLL_INTERVAL_MSNoNoPoll interval for container readiness (default: 300).
SANDBOX_LOG_LEVELNoNoContainer log level: debug, info, warn, error
SANDBOX_LOG_FORMATNoNoContainer log format: pretty or json
SANDBOX_TRANSPORTNoNoContainer transport: rpc
WORKER_URLYesNoPublic Worker URL (used for CDP and OTel endpoint)
WORKER_NAMEYesNoWorker name for config bucket key resolution
CF_AI_GATEWAY_ACCOUNT_IDYesYes*Cloudflare account ID for AI Gateway URL construction
CF_AI_GATEWAY_GATEWAY_IDYesYes*AI Gateway ID for URL construction
CF_AI_GATEWAY_CUSTOM_DOMAINNoNoAI Gateway custom domain (e.g. ai.example.com). Routes unified billing through https://<domain>/compat instead of api.cloudflare.com; required for Cloudflare Access identity-aware controls and User Insights attribution.
CF_ACCESS_TOKENNoYes*Cloudflare Access user JWT forwarded as cf-access-token on an Access-protected custom domain. AI Gateway records cf.user_id, which User Insights uses to attribute spend per identity.
CF_ACCESS_CLIENT_IDNoYes*Cloudflare Access service-token client ID (headless alternative; service tokens do not produce cf.user_id).
CF_ACCESS_CLIENT_SECRETNoYes*Cloudflare Access service-token client secret.
CF_ACCESS_TEAM_DOMAINNoYes*Cloudflare Access team domain (e.g., myteam.cloudflareaccess.com)
CLOUDFLARE_ACCOUNT_IDYesNoCloudflare account ID. Read by the Sandbox SDK for R2 presigned URL signing (backup/restore). Legacy CF_ACCOUNT_ID is deprecated (still mapped into the container by buildEnvVars).
BACKUP_BUCKET_NAMEYes§NoR2 backup bucket name (default: fouria-data). Required by the Sandbox SDK presigned-URL backup path; mapped to R2_BUCKET_NAME in container.
BACKUP_MAX_VERSIONSNoNoMax squashfs snapshot versions to retain (default: 3)
BACKUP_INTERVAL_MINUTESNoNoHow often to sync backup snapshots (default: 5)

† Mapped to OPENCLAW_DEV_MODE in container. ‡ Mapped to OPENCLAW_E2E_TEST_MODE in container. § Mapped to R2_BUCKET_NAME in container.

Mode behavior summary:

ModeCF Access AuthDevice PairingDebug RoutesWrite API
ProductionRequiredRequiredDisabledRBAC
DEV_MODEBypassedBypassedConfigurableROOT
E2E_TEST_MODEBypassedRequiredConfigurableROOT
DEMO_MODERequiredRequiredDisabledRead-only

Dynamic Secrets (F4E1-Encrypted R2 Storage)

Channel tokens, tool OAuth credentials, and custom integration secrets are managed by end-users through the integrations dashboard. They are stored as F4E1-encrypted JSON objects in the BACKUP_BUCKET R2 bucket under the secrets/<customerSlug>/ prefix.

Secret categories: ai_provider, channel, integration

The encryption uses the shared BACKUP_ENCRYPTION_KEY (same master key used for backup snapshots) with per-object HKDF-derived AES-256-GCM keys (domain-separated via fouria-r2-secrets/v1). The master key never leaves the Worker and is never passed to the container.

Unlike the old vault, there is no unlock/lock mechanism: secrets are always accessible when the key is configured. The /lock endpoint is a no-op kept for client compatibility. The WebSocket API (/ws) has been removed (returns 410 Gone).

See apps/fouria/src/services/secrets/R2SecretsStore.ts for the full implementation.


AI Provider Configuration

At least one provider must be configured. Priority: CF AI Gateway > Anthropic > OpenAI.

CI-Only Secret Mapping

The CI workflow maps GitHub secrets (prefixed with E2E_) to their corresponding env variables. See .github/workflows/test.yml for the authoritative mapping.

GitHub SecretMaps To
E2E_CLOUDFLARE_AI_GATEWAY_API_KEYCLOUDFLARE_AI_GATEWAY_API_KEY
E2E_CF_AI_GATEWAY_ACCOUNT_IDCF_AI_GATEWAY_ACCOUNT_ID
E2E_CF_AI_GATEWAY_GATEWAY_IDCF_AI_GATEWAY_GATEWAY_ID
E2E_TEARDOWN_API_TOKENCLOUDFLARE_TEARDOWN_API_TOKEN

There is no GitHub secret named CLOUDFLARE_AI_GATEWAY_API_KEY. Staging and production Lerma deploys (.github/workflows/deploy-dashboard.yml) both read E2E_CLOUDFLARE_AI_GATEWAY_API_KEY and deploy it as the CLOUDFLARE_AI_GATEWAY_API_KEY runtime env. Referencing a non-existent secrets.CLOUDFLARE_AI_GATEWAY_API_KEY expands to empty and ships a dashboard whose tenant provisioning fails with :missing_ai_gateway_credential — the deploy workflow fails fast when the secret is empty.

E2E_TEARDOWN_API_TOKEN is used only by the E2E teardown for destructive cleanup (wrangler delete + R2 bucket empty/delete + container/Access/service-token deletes). It must carry Workers Scripts, Workers KV Storage, Workers R2 Storage, Cloudflare Containers, and Access (Apps + Service Tokens) Edit permissions. When unset, teardown falls back to CLOUDFLARE_API_TOKEN (DEPLOY_WORKER_API_TOKEN in CI).

Secrets reference: See docs/schemas/secrets-manifest.schema.json for the canonical credential inventory.

Runtime AI Credentials

VariableClassContainerDescription
CLOUDFLARE_AI_GATEWAY_API_KEYPlatform SecretYesReal AI-scoped Cloudflare credential supplied by Lerma. It is never generated randomly per tenant.
CLOUDFLARE_API_TOKENTenant Runtime AliasYesBearer credential for the AI Gateway REST API. Lerma derives this tenant binding from CLOUDFLARE_AI_GATEWAY_API_KEY; it is not Lerma’s administrative provisioning token.

The native cloudflare-ai-gateway provider uses CLOUDFLARE_AI_GATEWAY_API_KEY. The unified-billing plugin uses CLOUDFLARE_API_TOKEN for api.cloudflare.com/client/v4/accounts/{account}/ai/v1 requests. When CF_AI_GATEWAY_CUSTOM_DOMAIN is set, the plugin switches to https://{domain}/compat and authenticates with CF_ACCESS_TOKEN (or a service token) when the domain is behind Cloudflare Access — this is what enables User Insights identity attribution via cf.user_id.

* At least one provider must be configured.


Authentication & Access Control

VariableClassContainerRequiredDescription
MOLTBOT_GATEWAY_TOKENInstance SecretYes†YesGateway token for CLI auth. Auto-generated per tenant by lerma provisioner.
CF_ACCESS_PLATFORM_AUDInstance SecretNoYes*Platform-level Access AUD (always present, from dispatch worker).
CF_ACCESS_CLIENT_AUDInstance SecretNoNoPer-tenant Access AUD (optional, provisioned by lerma).
CF_ACCESS_TEAM_DOMAINVariableNoYes*Cloudflare Access team domain (e.g., myteam.cloudflareaccess.com).

Secrets reference: See docs/schemas/secrets-manifest.schema.json for the canonical credential inventory.

* Required for production. Not required when DEV_MODE=true or E2E_TEST_MODE=true. † Mapped to OPENCLAW_GATEWAY_TOKEN by buildEnvVars().


Persistence & Storage

VariableClassContainerRequiredDescription
CLOUDFLARE_ACCOUNT_IDVariableYesNoCloudflare account ID. Read by the Sandbox SDK for R2 presigned URL signing (backup/restore). Legacy CF_ACCOUNT_ID is deprecated (still mapped into the container by buildEnvVars).
BACKUP_BUCKET_NAMEVariableYes†NoR2 backup bucket name (default: fouria-data). Required by the Sandbox SDK presigned-URL backup path alongside the BACKUP_BUCKET binding; mapped to R2_BUCKET_NAME in container.
R2_ACCESS_KEY_IDSecretYesYesR2 access key. Required by the Sandbox SDK presigned-URL backup path. Resolved per-instance by the lerma provisioner (shared lerma credential, or a generated per-instance token) and injected as a secret_text binding — never omitted silently.
R2_SECRET_ACCESS_KEYSecretYesYesR2 secret key, paired with R2_ACCESS_KEY_ID. Resolved per-instance by the lerma provisioner (see above).
BACKUP_MAX_VERSIONSVariableNoNoMax squashfs snapshot versions to retain (default: 3)
BACKUP_INTERVAL_MINUTESVariableNoNoHow often to sync backup snapshots (default: 5)
BACKUP_ENCRYPTION_KEYSecretNoRecommendedBase64 32-byte AES-256 master key for client-side R2 backup encryption (F4E1). Worker-side only, never passed to the container. Generate: openssl rand -base64 32
BACKUP_ENCRYPTION_REQUIREDVariableNoNoSet to true to fail closed (skip backup + error) when BACKUP_ENCRYPTION_KEY is missing (default: false, legacy plaintext + warning)

† Mapped to R2_BUCKET_NAME in container.

Secrets reference: See docs/schemas/secrets-manifest.schema.json for the canonical credential inventory.


Cloudflare Tunnel

Secrets reference: See docs/schemas/secrets-manifest.schema.json for the canonical credential inventory.


Browser Automation (CDP)

VariableClassContainerRequiredDescription
WORKER_URLVariableYesNoPublic Worker URL (used for CDP and OTel endpoint)

Secrets reference: See docs/schemas/secrets-manifest.schema.json for the canonical credential inventory.


Container Rescue (/debug/rescue-container)

Recovers a container stuck in Error mode (e.g. the fouria:unstable-<sha> image it references was deleted by the stale-image cleanup cron). The tool probes the container via the Sandbox SDK, re-points the application image to the expected tag through the Cloudflare Containers API (rollout), and destroys the stuck container so the platform provisions a fresh one. See apps/fouria/src/diagnostics.ts.

VariableClassContainerRequiredDescription
CLOUDFLARE_API_TOKENRuntime SecretYesNoAI Gateway REST Bearer token on tenant workers. On ROOT/ops deployments the same binding may also be used by /debug/rescue-container when it has Containers:Edit.
CONTAINER_APPLICATION_IDVariableNoNoCloudflare Containers application id for this worker’s Sandbox container (set by the provisioner).
CONTAINER_IMAGE_TAGVariableNoNoExpected container image tag (e.g. unstable-<sha>); used to derive the repair target registry.cloudflare.com/<account>/fouria:<tag>.
CONTAINER_IMAGE_NAMEVariableNoNoContainer image repo name for the tenant sandbox image (default: fouria).

Secrets reference: See docs/schemas/secrets-manifest.schema.json for the canonical credential inventory.


VariableClassContainerRequiredDescription
OTEL_SHARED_SECRETInstance SecretYesNoShared secret for OTel metric ingest authentication
OTEL_EXPORTER_OTLP_METRICS_ENDPOINTAutoYesAutoAuto-derived from WORKER_URL + /api/otel/v1/metrics
OTEL_EXPORTER_OTLP_PROTOCOLAutoYesAutoAuto-set to http/json
OTEL_SERVICE_NAMEAutoYesAutoAuto-set to customer slug or worker name

Feature Flags

VariableClassContainerRequiredDescription
MOLTLAZY_FEATURE_FLAGSAutoYesAutoJSON feature flags for moltlazy config generation (auto-generated).
WORKER_NAMEVariableYesNoWorker name for config bucket key resolution.
STARTUP_LOG_VERBOSITYVariableYesNoClean boot-log verbosity for the loading page: steps | detailed (default) | verbose.

Stripe Billing Integration

Instance secrets for the Stripe subscription billing and payment management features in the Admin UI. Only accessed by the Worker (not the container).

VariableClassContainerRequiredDescription
STRIPE_SECRET_KEYInstance SecretNoNoStripe secret key (sk_live_...) for creating Checkout and Customer Portal sessions.
STRIPE_PUBLISHABLE_KEYInstance SecretNoNoStripe publishable key (pk_live_...), exposed to the frontend for Stripe.js.

Billing data (plan tier, status, seats, credits, Stripe customer ID) is sourced from the Supabase database via the Dispatch Worker. The Worker does not persist billing state in Durable Objects.

Communication Schema (Dispatch Worker → User Worker)

Billing metadata is passed from the Dispatch Worker to the User Worker via one of three resolution methods (in priority order):

PriorityMethodHeader / SourceFormat
1 (preferred)Cloudflare for SaaS custom hostname metadatarequest.cf.hostMetadataJSON object — set via PATCH /zones/:id/custom_hostnames/:id with custom_metadata
2Single metadata headerX-FourIA-MetadataBase64url-encoded JSON of BillingMetadata
3 (legacy)Individual headersX-FourIA-Plan, X-FourIA-SeatsUsed, etc.String values (deprecated)

Preferred approach (1): Set custom_metadata on each customer’s custom hostname via the Cloudflare API. The Dispatch Worker reads request.cf.hostMetadata and the User Worker reads it when trusted_workers: true is set on the dispatch namespace.

Fallback approach (2): The Dispatch Worker encodes the BillingMetadata object as base64url JSON and sets it as the X-FourIA-Metadata header when forwarding the request.

BillingMetadata Schema

interface BillingMetadata {
  plan_tier: string; // "free" | "basic" | "pro" | "enterprise"
  plan_name: string; // Human-readable name
  billing_status: string; // "active" | "past_due" | "canceled" | "trialing" | "incomplete"
  seats_used: number; // Current seat count
  seats_max: number; // Plan seat limit
  credits_used: number; // Credits consumed this period
  credits_max: number; // Credit limit (0 = unlimited/pay-as-you-go)
  stripe_customer_id: string | null; // Stripe customer for payments
  period_end: string | null; // ISO 8601 — current billing period end
  trial_end: string | null; // ISO 8601 — trial end (if trialing)
}

RBAC Role Resolution

The user’s RBAC role is forwarded by the Dispatch Worker via the X-FourIA-Role header (one of ROOT, ADMIN, BASE_USER), when the Dispatch Worker can resolve it:

  • Paso4 ROOT users are always ROOT. A user qualifies when their JWT aud is the platform AUD (the *.fouria.io wildcard Access application) or their email is a member of the shared Paso4 Root Users Access group. The group check matters because per-instance Access applications include the ROOT group policy (so ROOT operators are not locked out at {slug}.fouria.io); Cloudflare matches the most-specific application, so those tokens carry the per-instance AUD rather than the platform AUD. In staging this is the only permitted audience/identity.
  • Client users (per-instance Access application AUDs) get their role only when client_users.status = validated and client_user_instances links them to the requested instance. Pending, rejected, production-only, or dev-only assignments do not cross environment boundaries.

The User Worker’s resolveCustomerRole resolves the role in priority order:

  1. JWT aud matches CF_ACCESS_PLATFORM_AUDROOT
  2. X-FourIA-Role header (from the Dispatch Worker)
  3. JWT custom.role claim (IdP-provided)
  4. LOCAL_ROLE_MAP (local dev fallback)

Dashboard Tenant Provisioning (apps/lerma)

Variables required by the Phoenix dashboard to create and decommission tenant User Workers.

CF Access Local Development

Variables for local CF Access integration via cloudflared tunnel. See apps/lerma/bin/setup-cf-access-local.sh for one-time setup.

VariableClassRequiredDescription
CF_ACCESS_TEAM_DOMAINVariableYes*Cloudflare Access team domain (e.g., paso4). Used by lerma’s dashboard auth and deployed to tenant workers.
CF_ACCESS_AUDVariableYes*Dashboard’s own Access AUD (JWT audience for lerma’s dashboard). Populated by the deploy flow from CF_ACCESS_LERMA_AUD/STAGING_CF_ACCESS_LERMA_AUD.
CF_ACCESS_PLATFORM_AUDVariableYes*Platform-level Access AUD (Paso4 Root app). Deployed to every tenant worker as the CF_ACCESS_PLATFORM_AUD secret so its auth middleware can validate JWTs forwarded by the Dispatch Worker.
CF_ACCESS_SERVICE_TOKEN_IDInstance SecretNoCF Access service token Client ID for local CI/CD (non-interactive).
CF_ACCESS_SERVICE_TOKEN_SECRETInstance SecretNoCF Access service token Client Secret for local CI/CD.
CLOUDFLARED_TUNNEL_TOKENInstance SecretNoTunnel token for lerma-local (routes to port 4001).
SKIP_AUTHVariableNoSet to "true" to bypass CF Access JWT verification (escape hatch).

Invitation Email

Lerma delivers invitation confirmation emails through Cloudflare Email Service over authenticated SMTP submission (smtp.mx.cloudflare.net:465, implicit TLS). It reuses the existing provisioning credential as the SMTP password, so no additional secret is required.

VariableClassRequiredDescription
LERMA_API_TOKENSecretYesCloudflare API token. Must include the Email Sending: Edit permission; used as the SMTP password (username is the literal api_token). The From domain must be onboarded for Email Sending.

Local development continues to use Swoosh’s local mailbox adapter; tests use the Swoosh test adapter.

Provisioning Configuration

VariableClassRequiredDescription
CLOUDFLARE_API_TOKENENV onlyYesAPI token with Workers:Edit, R2:Edit, Containers:Edit, and D1 edit permissions. Containers:Edit is required — the dashboard generates short-lived registry credentials (POST /containers/registries/{host}/credentials) to list versions, and uses R2:Edit to fetch the worker bundle from the fouria-bundles R2 bucket at provisioning time. Without it the calls fail with 403 Authentication error.
CLOUDFLARE_ACCOUNT_IDVariableYesPaso4 Cloudflare account ID for provisioning calls. Must be set or the registry-health check reports it missing and registry-credentials calls fail with Could not route to /accounts/containers/registries/... — object identifier is invalid (the account ID is interpolated into the request path). On the fouria User Worker it is a plain var: the Sandbox SDK reads it for R2 presigned URL signing (backup/restore) — missing it throws InvalidBackupConfigError (Backup requires R2 presigned URL credentials). The dashboard health check also validates that CLOUDFLARE_API_TOKEN grants the required permission groups (Workers Scripts Write, DNS Write, Access: Apps and Policies Write, Workers Containers Write), flagging a missing one as “Limited access”. Legacy CF_ACCOUNT_ID is deprecated.
CONTAINER_REGISTRY_HOSTVariableNoCloudflare managed container registry host (default: registry.cloudflare.com). Mirrors CLOUDFLARE_CONTAINER_REGISTRY in wrangler.
CONTAINER_IMAGE_NAMEVariableNoContainer image repo name for the tenant sandbox image (default: fouria).
CONTAINER_BUNDLE_R2_BUCKETVariableNoR2 bucket that publishes the worker bundle fouria.mjs (default: fouria-bundles). CI uploads the bundle to <version>/fouria.mjs and the dashboard fetches it from R2 at provisioning time.
FOURIA_DISPATCH_NAMESPACEVariableNoWorkers for Platforms dispatch namespace name (default: fouria-dispatch).
FOURIA_R2_PUBLIC_ENDPOINTVariableNoR2 public endpoint template, e.g. https://pub-{bucket_id}.r2.dev.
ANALYTICS_DATASETVariableNoAnalytics Engine dataset name (default: fourai_cost_metrics, staging: fourai_cost_metrics_staging).
CONTAINER_IMAGEVariableNoContainer image reference for the tenant’s container application (linked to the Sandbox DO namespace). Example: registry.cloudflare.com/<account_id>/fouria:latest. When unset, lerma derives it from the published bundle version as registry.cloudflare.com/<account_id>/fouria:<image_tag>.
CONTAINER_MAX_INSTANCESVariableNoMax container instances for tenant container apps (default: 3).
AI_GATEWAY_CREATE_DEDICATEDVariableNoAllow creating a dedicated per-tenant AI Gateway when the shared gateway pool has no capacity (default: true). Set to false to enforce reuse of pooled gateways — provisioning then fails with no_ai_gateway_capacity instead of creating a new gateway. See docs/CLOUD-ARCHITECTURE.md (AI Gateway reuse) and the dashboard at /dashboard/ai-gateways.
WORKER_OBSERVABILITY_ENABLEDVariableNoEnable Workers Observability (logs + traces) on deployed tenant User Workers (default: true). Mirrors apps/fouria/wrangler.jsonc.
WORKER_OBSERVABILITY_SAMPLING_RATEVariableNoHead sampling rate for traces on tenant User Workers, 0..1 (default: 1).
WORKER_OBSERVABILITY_LOGSVariableNoEnable logs on tenant User Workers (default: true).
WORKER_OBSERVABILITY_INVOCATION_LOGSVariableNoEnable invocation logs on tenant User Workers (default: true).
WORKER_OBSERVABILITY_TRACESVariableNoEnable traces on tenant User Workers (default: true).
CONTAINER_OBSERVABILITY_LOGSVariableNoEnable log capture on the tenant’s container application (default: true).
CONTAINER_REGIONSVariableNoContainer application placement regions (deployment zone), comma-separated (default: WEUR). Valid values: ENAM, WNAM, EEUR, WEUR, APAC, SAM, ME, OC, AFR.
R2_TOKEN_STRATEGYVariableNoHow lerma resolves the mandatory per-tenant R2 S3 backup credential: auto (default), shared, or per_instance. auto uses R2_TOKEN_MAX_PER_ACCOUNT: a known limit <= 250 selects shared, > 250 or unknown selects per_instance. per_instance mints a scoped R2 token per instance at provisioning. See Lerma.Provisioning.R2Credentials.
R2_TOKEN_MAX_PER_ACCOUNTVariableNoAccount API-token limit used by :auto (default: unknown → :per_instance). Cloudflare exposes no endpoint for the maximum, so pin a known value here if desired.
R2_ACCESS_KEY_IDSecretCond.Lerma-level shared R2 S3 access key, required when the effective strategy is shared. Injected into every tenant worker as a secret_text binding. Generate with mix lerma.r2_token --all-buckets.
R2_SECRET_ACCESS_KEYSecretCond.Lerma-level shared R2 S3 secret key, paired with R2_ACCESS_KEY_ID. Required when the effective strategy is shared; provisioning fails with :missing_shared_r2_credentials otherwise.

Health Monitoring

Lerma’s HealthMonitor periodically probes each tenant’s public GET /api/health endpoint and keeps clients.status and instances.status accurate. /api/health is the unified health endpoint (returns startup/readiness/liveness probes plus the canonical status and processId); GET /api/status is a legacy alias over the same snapshot. Both are exposed by every User Worker without auth and are made publicly reachable via path-scoped Cloudflare Access applications (*.fouria.io/api/health, *.fouria.io/api/status, see apps/fouria/iac/access.tf). Probing also cold-starts asleep containers (ensureGateway), so a stopped VM recovers on the next check — pass ?passive=1 (used by ROOT diagnostics) to probe without warming.

VariableClassRequiredDescription
HEALTH_MONITOR_ENABLEDVariableNoEnable the periodic health monitor (default: true). Always disabled in test env.
HEALTH_MONITOR_INTERVAL_MSVariableNoPoll interval in milliseconds (default: 120000).
HEALTH_MONITOR_TIMEOUT_MSVariableNoPer-probe HTTP timeout in milliseconds (default: 15000).
HEALTH_MONITOR_FAILURE_THRESHOLDVariableNoConsecutive failed probes before the client transitions production → error (default: 3). A healthy probe flips it back to production.
HEALTH_MONITOR_PLATFORM_DOMAINVariableNoDomain the tenant workers are served under (default: fouria.io).
HEALTH_MONITOR_SLUG_PREFIXVariableNoHostname prefix for staging tenants (default: "", staging auto-set to staging-).

How Secrets Are Deployed

The provisioner deploys all instance secrets as secret_text bindings directly on the User Worker. These are encrypted at rest by Cloudflare and available at runtime as plain string values (not Secrets Store objects).

The provisioner also creates an empty Cloudflare Secrets Store (fouria-{slug}) bound to the Worker for future dynamic secret use. Instance secrets are never placed in this store.

# Per-tenant secrets (generated by lerma provisioner)
%{type: "secret_text", name: "MOLTBOT_GATEWAY_TOKEN", text: gateway_token}
%{type: "secret_text", name: "CDP_SECRET", text: value}
%{type: "secret_text", name: "CF_ACCESS_PLATFORM_AUD", text: platform_aud}
%{type: "secret_text", name: "CF_ACCESS_CLIENT_AUD", text: tenant_aud}  # Optional

# Platform-level secrets (from Lerma ENV, optional)
%{type: "secret_text", name: "CLOUDFLARE_AI_GATEWAY_API_KEY", text: ai_gateway_key}
%{type: "secret_text", name: "CLOUDFLARE_API_TOKEN", text: ai_gateway_key}  # Tenant REST alias
%{type: "secret_text", name: "STRIPE_SECRET_KEY", text: value}
# ... etc.

See apps/lerma/lib/lerma/provisioning/tenant_provisioner.ex for the authoritative binding list.

Container-Backed Durable Objects

The tenant User Worker’s Sandbox DO class is a container-backed Durable Object (it extends Container from @cloudflare/sandbox). lerma deploys the worker into the dispatch namespace with a containers array in the upload metadata (declaring Sandbox), which enables the container runtime for that class — without it, Cloudflare throws Containers have not been enabled for this Durable Object class when the worker calls into the container (e.g. /api/doctor). lerma also creates/updates a container application for the tenant (image + instance type + max instances) linked to the Sandbox DO namespace.

Container app image comes from CONTAINER_IMAGE, and the container instance type is derived from the instance type chosen at provisioning time (defaults to lite, the cheapest tier). CONTAINER_MAX_INSTANCES configures the max instances (see table above).

The tenant container application is pinned to the WEUR deployment zone by default (CONTAINER_REGIONS) and container log capture is enabled (CONTAINER_OBSERVABILITY_LOGS). Observability for the User Worker itself (logs + traces + invocation logs) mirrors the apps/fouria/wrangler.jsonc block and is enabled by default (WORKER_OBSERVABILITY_* vars above).


Legacy Channel Secrets (E2E Testing Only)

Deprecated. Channel secrets are now managed through the integrations dashboard (F4E1-encrypted R2 vault). These env vars are kept only for E2E test fixtures. Do not use in production.

Secrets reference: See docs/schemas/secrets-manifest.schema.json for the canonical credential inventory.


Internal Variables (Auto-Set)

These are automatically derived by the Worker and should never be manually configured:

VariableSource
OPENCLAW_GATEWAY_TOKENMapped from MOLTBOT_GATEWAY_TOKEN
OPENCLAW_DEV_MODEMapped from DEV_MODE
OPENCLAW_E2E_TEST_MODEMapped from E2E_TEST_MODE
CUSTOMER_SLUGSet from tenant resolution
OTEL_EXPORTER_OTLP_HEADERSAuto: Authorization=Bearer {OTEL_SHARED_SECRET}
OTEL_EXPORTER_OTLP_METRICS_ENDPOINTAuto: {WORKER_URL}/api/otel/v1/metrics
MOLTLAZY_FEATURE_FLAGSSet as per-customer binding (managed via FourIA API)
MOLTLAZY_ANALYTICS_DATASETAuto-set to tenant Analytics Engine dataset
CUSTOMER_ANALYTICS_DATASETAuto-set to tenant Analytics Engine dataset
STARTUP_LOG_VERBOSITYPassed through from Worker env when set (detailed default in container)
OPENCLAW_CONFIG_PATHBaked into image: /root/openclaw/.openclaw/openclaw.json — the REAL config path (see below)
OPENCLAW_STATE_DIRBaked into image: /root/openclaw/.openclaw — the REAL state dir (see below)

Why OPENCLAW_CONFIG_PATH / OPENCLAW_STATE_DIR point at /root/openclaw/.openclaw: HOME=/home/openclaw and /home/openclaw/.openclaw is a symlink to the real config dir at /root/openclaw/.openclaw (kept under the /home/openclaw snapshot root for R2 backups). OpenClaw 2026.8.2+ fs-safe refuses atomic file replaces when the immediate parent directory is a symlink (“Atomic replace parent must be a real directory”), so config/state writes through the /home symlink fail. These two env vars point OpenClaw at the real path for its own writes; the /home/openclaw/.openclaw symlink is preserved so backups and the Worker’s /home-based config reads/writes keep working. Set in the Dockerfile (apps/fouria/Dockerfile), not per-tenant.


Setting Variables

Local Development (.dev.vars)

Copy from .dev.vars.example:

cp apps/fouria/.dev.vars.example apps/fouria/.dev.vars
# Edit .dev.vars with your values
# .dev.vars is gitignored — never commit it

In local dev, instance secrets are plain strings (simulating secret_text bindings). Dynamic secrets need BACKUP_ENCRYPTION_KEY configured with the F4E1 R2 store.

Production Deployment

Instance secrets are deployed automatically by lerma when a tenant is provisioned. No manual wrangler secret put commands are needed.

Platform-level secrets should be set as environment variables in the dashboard’s deployment:

STRIPE_SECRET_KEY=sk_live_...
OTEL_SHARED_SECRET=your-secret
# ... etc.

OpenTofu (IaC) Mapping

Secrets reference: See docs/schemas/secrets-manifest.schema.json for the canonical credential inventory.