owlette docs
self-hosting

environment variables

Complete reference for all environment variables used by the owlette web dashboard.


firebase client (required)

These are exposed to the browser (client-side). The NEXT_PUBLIC_ prefix is required by Next.js.

variableexamplesource
NEXT_PUBLIC_FIREBASE_API_KEYAIzaSy...Firebase Console → Project Settings → Web App
NEXT_PUBLIC_FIREBASE_AUTH_DOMAINmy-project.firebaseapp.comSame
NEXT_PUBLIC_FIREBASE_PROJECT_IDmy-project-idSame
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKETmy-project.appspot.comSame
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID123456789012Same
NEXT_PUBLIC_FIREBASE_APP_ID1:123:web:abcSame

These values are public by design — Firebase client SDKs are designed to be used in browsers. Security is enforced by Firestore security rules, not by keeping these values secret.


firebase admin (required)

Server-side only — used for generating agent OAuth tokens and verifying sessions. Use three separate variables (not a JSON blob).

variableformatsource
FIREBASE_PROJECT_IDmy-project-idFirebase Console → Project Settings → General
FIREBASE_CLIENT_EMAILfirebase-adminsdk-xxx@my-project.iam.gserviceaccount.comFirebase Console → Service Accounts → Generate Key
FIREBASE_PRIVATE_KEY"-----BEGIN PRIVATE KEY-----\n..."Same — keep the \n escape sequences

When setting FIREBASE_PRIVATE_KEY in Railway, wrap the value in double quotes and preserve the \n newline escapes exactly as exported from Firebase.

The web runtime reads the split variables above from web/lib/firebase-admin.ts. FIREBASE_SERVICE_ACCOUNT_KEY JSON blobs are legacy documentation only for this app and are not read by the current server code.


session management (required)

variableformatdescription
SESSION_SECRET32+ character stringEncryption key for iron-session HTTPOnly cookies
MFA_ENCRYPTION_KEY32+ character stringEncryption key for 2FA secrets stored in Firestore

Generate a secure value:

python -c "import secrets; print(secrets.token_hex(32))"

email (required for alerts)

variabledescription
RESEND_API_KEYAPI key from Resend for sending emails
RESEND_FROM_EMAILVerified sender address (e.g. alerts@owlette.app)
ADMIN_EMAIL_PRODFallback admin email address (production)
ADMIN_EMAIL_DEVFallback admin email address (development)
SEND_WELCOME_EMAILtrue or false — controls welcome emails to new users

rate limiting (required)

Owlette uses Upstash Redis for API rate limiting. Create a free Serverless Redis database and copy the REST connection details.

variabledescription
UPSTASH_REDIS_REST_URLREST API URL for your Upstash Redis instance
UPSTASH_REDIS_REST_TOKENAuthentication token for Upstash Redis

cloudflare r2 (required for roost)

The roost upload, chunk-check, and version-body routes use Cloudflare R2 through its S3-compatible API. The web runtime reads these variables in web/lib/r2Client.server.ts.

variableformatdescription
R2_S3_ENDPOINThttps://<account-id>.r2.cloudflarestorage.comS3-compatible endpoint. This is where the Cloudflare account id is configured at runtime.
R2_S3_ACCESS_KEY_IDR2 access key idS3-compatible R2 token with Object Read & Write access to the owlette buckets
R2_S3_SECRET_ACCESS_KEYR2 secret access keyMatching secret for R2_S3_ACCESS_KEY_ID

Bucket names are not environment variables in the current web code. bucketFor() derives them from the active roost environment:

roost envcontent bucketmanifests bucket
prodowlette-prod-contentowlette-prod-manifests
devowlette-dev-contentowlette-dev-manifests

ROOST_ENV=prod or ROOST_ENV=dev can override environment detection. Without that override, production is selected when RAILWAY_ENVIRONMENT=production or RAILWAY_PUBLIC_DOMAIN=owlette.app; otherwise the code defaults to dev.

There is no separate public URL prefix runtime variable. R2 buckets should remain private; upload and download access uses presigned URLs. Version metadata URLs are derived from the selected manifests bucket and R2_S3_ENDPOINT.

CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_R2_API_TOKEN are provisioning inputs for scripts/provision-r2.mjs, not web runtime variables.


url configuration (required in production)

variabledescription
NEXT_PUBLIC_BASE_URLPublic base URL used in email links and agent callbacks (e.g. https://owlette.app)
RAILWAY_PUBLIC_DOMAINRailway deployment domain — auto-injected by Railway, override if using a custom domain

cron (required for health checks)

variabledescription
CRON_SECRETShared secret for authenticating cron health-check requests

Generate:

python -c "import secrets; print(secrets.token_hex(32))"

public status page (required for external launch)

These are server-side only. They wire /api/cron/status-ping to the hosted public status page.

variabledescription
OWLETTE_STATUS_BASE_URLOptional base URL for synthetic checks; defaults to the request origin or https://owlette.app
INSTATUS_API_KEYInstatus API token
INSTATUS_PAGE_IDInstatus hosted page id; not tied to the later status.owlette.app custom-domain upgrade
INSTATUS_API_BASE_URLOptional API override; defaults to https://api.instatus.com
INSTATUS_COMPONENT_STATUS_METHODOptional component update method; defaults to PUT
INSTATUS_COMPONENT_STATUS_URL_TEMPLATEOptional component update URL template using {pageId} and {componentId} placeholders; default is /v2/{pageId}/components/{componentId} under INSTATUS_API_BASE_URL
INSTATUS_COMPONENT_DASHBOARD_IDComponent id for dashboard
INSTATUS_COMPONENT_API_IDComponent id for API
INSTATUS_COMPONENT_AGENT_REGISTRY_IDComponent id for agent registry
INSTATUS_COMPONENT_WEBHOOK_DELIVERY_IDComponent id for webhook delivery
INSTATUS_COMPONENT_R2_UPLOADS_IDComponent id for R2 uploads
INSTATUS_COMPONENT_FIRESTORE_IDComponent id for Firestore
INSTATUS_COMPONENT_CORTEX_CHAT_IDComponent id for Cortex chat

Do not store the Instatus API key in docs or screenshots. Record only component ids and env var names in the operator reference.


public API launch state

There is no environment variable that enables the public API launch. The launch state is tracked in the public API launch ticket and runbook, while API routes continue to enforce authentication, scopes, rate limits, and audit behavior.

Do not add an OWLETTE_PUBLIC_API_LAUNCHED style flag unless the web code also enforces that flag on the relevant routes and the rollback behavior is documented.


encryption (required for llm keys)

variabledescription
LLM_ENCRYPTION_KEY32-byte hex key for encrypting stored LLM API keys

Generate:

python -c "import secrets; print(secrets.token_hex(32))"

autonomous cortex (optional)

variabledescription
CORTEX_INTERNAL_SECRETShared secret for internal auth between alert route and autonomous Cortex endpoint

Generate:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Required only if you want autonomous Cortex (AI auto-investigates process crashes). Also requires a site-level LLM API key configured in Firestore (sites/{siteId}/settings/llm) and autonomous mode enabled (sites/{siteId}/settings/cortexautonomousEnabled: true).


observability (optional)

variabledescription
NEXT_PUBLIC_SENTRY_DSNSentry DSN. Sentry is disabled when this is unset.
NEXT_PUBLIC_SENTRY_ENVIRONMENTSentry environment name such as development, staging, or production.
SENTRY_AUTH_TOKENOptional source-map upload token for CI/Railway builds.
SENTRY_ORGOptional Sentry organization slug for source-map upload.
SENTRY_PROJECTOptional Sentry project slug for source-map upload.
SECURITY_BOUNDARY_SENTRY_METRICSOptional flag for security-boundary metric emission.

environment (auto-set)

variablevalueset by
NODE_ENVproductionrailway.toml
PORT3000Railway (auto-injected)
RAILWAY_ENVIRONMENTproduction or Railway environment nameRailway (auto-injected)

summary

minimum required

# Firebase Client
NEXT_PUBLIC_FIREBASE_API_KEY=...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=...
NEXT_PUBLIC_FIREBASE_PROJECT_ID=...
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=...
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=...
NEXT_PUBLIC_FIREBASE_APP_ID=...

# Firebase Admin
FIREBASE_PROJECT_ID=my-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxx@my-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."

# Session
SESSION_SECRET=your-32-char-secret
MFA_ENCRYPTION_KEY=your-32-char-mfa-key

# Rate Limiting
UPSTASH_REDIS_REST_URL=https://...upstash.io
UPSTASH_REDIS_REST_TOKEN=...

# Cloudflare R2
R2_S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
R2_S3_ACCESS_KEY_ID=...
R2_S3_SECRET_ACCESS_KEY=...

# URL
NEXT_PUBLIC_BASE_URL=https://your-app.railway.app

# Cron
CRON_SECRET=your-64-char-hex

full configuration

# Firebase Client
NEXT_PUBLIC_FIREBASE_API_KEY=...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=...
NEXT_PUBLIC_FIREBASE_PROJECT_ID=...
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=...
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=...
NEXT_PUBLIC_FIREBASE_APP_ID=...

# Firebase Admin
FIREBASE_PROJECT_ID=my-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxx@my-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."

# Session
SESSION_SECRET=your-32-char-secret
MFA_ENCRYPTION_KEY=your-32-char-mfa-key

# Email
RESEND_API_KEY=re_...
RESEND_FROM_EMAIL=alerts@yourdomain.com
ADMIN_EMAIL_PROD=admin@yourdomain.com
ADMIN_EMAIL_DEV=dev@yourdomain.com
SEND_WELCOME_EMAIL=true

# Rate Limiting
UPSTASH_REDIS_REST_URL=https://...upstash.io
UPSTASH_REDIS_REST_TOKEN=...

# Cloudflare R2
R2_S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
R2_S3_ACCESS_KEY_ID=...
R2_S3_SECRET_ACCESS_KEY=...
ROOST_ENV=prod

# URL
NEXT_PUBLIC_BASE_URL=https://owlette.app
RAILWAY_PUBLIC_DOMAIN=owlette.app

# Cron
CRON_SECRET=your-64-char-hex

# Public Status Page
OWLETTE_STATUS_BASE_URL=https://owlette.app
INSTATUS_API_KEY=...
INSTATUS_PAGE_ID=...
INSTATUS_COMPONENT_DASHBOARD_ID=...
INSTATUS_COMPONENT_API_ID=...
INSTATUS_COMPONENT_AGENT_REGISTRY_ID=...
INSTATUS_COMPONENT_WEBHOOK_DELIVERY_ID=...
INSTATUS_COMPONENT_R2_UPLOADS_ID=...
INSTATUS_COMPONENT_FIRESTORE_ID=...
INSTATUS_COMPONENT_CORTEX_CHAT_ID=...

# LLM Encryption
LLM_ENCRYPTION_KEY=your-64-char-hex

# Autonomous Cortex (optional)
CORTEX_INTERNAL_SECRET=your-64-char-hex

# Observability (optional)
NEXT_PUBLIC_SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
NEXT_PUBLIC_SENTRY_ENVIRONMENT=production
SENTRY_AUTH_TOKEN=sntrys_...
SENTRY_ORG=your-org
SENTRY_PROJECT=owlette-web

security notes

  • Never commit .env.local to git
  • Use Railway's Variables tab — values are encrypted at rest
  • NEXT_PUBLIC_* prefix means the value is exposed to the browser — only use for Firebase client config
  • Rotate secrets periodically — especially SESSION_SECRET and CRON_SECRET
  • Separate environments — use different Firebase projects for dev and production

on this page