Skip to content
View as .md

Environment variables

Env is schema-driven with varlock. Every key the apps read is declared once, with its type, whether it is secret, and whether it is required, in a committed .env.schema; values come from the schema’s public defaults, a gitignored .env.local, or the platform (Cloudflare, Fly, CI). bunx varlock load names anything missing or malformed before an app boots; bunx varlock load --agent prints the resolved env with secrets redacted, which is the only way an agent should inspect it.

.env.schema # shared: Supabase, durable streams, PostHog, LLM keys, Daytona, Giphy
apps/www/.env.schema # + OpenRouter management key, feedback key, release identity
apps/backstage/.env.schema # + DATABASE_URL, director overrides, worker id
packages/sandbox/src/daytona/proofs/.env.schema # + proof knobs (stream url, house/thread ids)
.env.local # gitignored secrets for every app (also apps/<app>/.env.local for app-only ones)

Each app schema imports the root directory (@import(../../)), so the root schema’s defaults and the root .env.local reach every app; an app’s own .env.local wins over both. Secrets are anything not prefixed PUBLIC_ unless the schema marks it @public. The environment flag is APP_ENV, resolved from the platform when unset (main → production, PRs → preview, vitest → test, laptop → development).

How each surface loads it:

  • www dev/build@varlock/vite-integration in apps/www/vite.config.ts validates the schema and injects the resolved env before SvelteKit reads $env/static/* and $env/dynamic/*. hooks.server.ts still re-checks the six core vars at module load.
  • www prod (Cloudflare)apps/www/scripts/deploy-worker.ts deploys with varlock-wrangler, not plain wrangler: it resolves the schema from the Builds environment, uploads non-sensitive values as worker vars and sensitive ones as secrets, and injects the __VARLOCK_ENV blob the SSR loader reads at boot. Keys read via $env/static/private must still exist as Builds secrets or the build fails with MISSING_EXPORT; OPENROUTER_API_KEY is read both ways. The worker’s runtime env now comes from the schema, so there are no hand-managed wrangler secrets; varlock also passes --keep-vars=false, dropping any var not in the schema. RELEASE_SHA is put in the process env by the deploy script so varlock uploads it. PR previews need the same treatment: the non-production Builds command must be bun run --filter '@arbe/www' deploy:preview, not plain wrangler versions upload.
  • backstage prod (Fly) — every value is a Fly app secret; the Docker entrypoint is plain bun run, so the schema is not consulted there yet. apps/backstage/src/index.ts names missing vars at boot. Locally bun run start goes through varlock run.
  • scriptsbunx varlock run -- bun run <script> from the directory whose schema applies (repo root for most; the proofs dir for prove-*.ts). Bun’s own .env autoload is off at the root (bunfig.toml).
  • CIbun run check:env validates root, www, and backstage schemas before the builds, with placeholder secrets from the workflow env.

Gotchas that the schema comments also carry: SUPABASE_JWT_SECRET is the raw HS256 secret from Supabase → Settings → API → JWT Secret, not a JWT and not the service role key (wrong value → “No suitable key”). PostHog must be set on www and Fly, or HTTP-side spans stay visible while chat $ai_generation spans disappear (analytics). LLM keys (OPENROUTER_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY) are overridden per house by a house secret of the same name; GITHUB_TOKEN is only ever a house/sandbox secret. RELEASE_SHA is a wrangler --var, and ARBE_RELEASE_SHA takes precedence over Cloudflare’s WORKERS_CI_COMMIT_SHA when deploying from a source archive.

Secret storage: plaintext .env.local values can be encrypted in place with bunx varlock encrypt --file .env.local (device-bound, revealed with bunx varlock reveal <NAME>).

Code: .env.schema and the three app schemas, apps/www/vite.config.ts, apps/www/src/hooks.server.ts, apps/backstage/src/index.ts.
See system/deployment, system/development, system/auth, system/environments.