Infisical environment & secrets guide (apps/web)

Infisical environment & secrets guide (apps/web)

Secrets never live in apps/web/.env. That file is empty on purpose. All
runtime secrets come from Infisical and are injected into the process by the
Infisical CLI. The workspace is configured in .infisical.json
(workspaceId 66410021-9c0a-4075-803b-f9286f21145f).

  • apps/web/.env.example is documentation only. It is the catalogue of
    every variable the web app understands, with a comment per key. Do not copy
    it into a real .env and paste secret values there; nothing loads it at
    runtime. Read it to learn what a key means, never to source its value.

  • To run anything that needs secrets (the dev server, scripts, one-off tasks),
    wrap the command in the Infisical CLI so it fetches the dev environment and
    exports the variables into the child process:

    # canonical dev server (defined as a package.json script)
    bun run dev:infisical        # = infisical run --env=dev -- vite dev
    
    # any other secret-needing command uses the same wrapper:
    infisical run --env=dev -- <command>
    
  • apps/web/.env is intentionally empty. Do not hand-fill it. If you need a
    value locally that Infisical does not hold, set it in your shell or pass it
    inline rather than committing it.

  • The config loader (src/server/config.ts) resolves each key as
    infisicalSnapshot[key] ?? process.env[key], trimmed, so a value present in
    Infisical wins over a same-named shell var. An unconfigured Infisical (no
    INFISICAL_CLIENT_ID/SECRET/PROJECT_ID) is a supported no-op and everything
    falls back to plain env vars.

  • Infisical’s dev environment’s R2_* and SESSION_SECRET are intentionally
    kept in sync with the team’s local Garage setup (see
    .agents/docs/home-worker-local-run.md) rather than pointing at real
    Cloudflare R2 — a deliberate choice while the team is small (2 devs).
    Anyone expecting dev:infisical to hit real R2 needs to know this changed;
    reverting means resetting R2_ENDPOINT/R2_ACCESS_KEY_ID/
    R2_SECRET_ACCESS_KEY/R2_BUCKET in Infisical dev back to real R2 values
    and removing R2_REGION/R2_FORCE_PATH_STYLE.

  • The CLI reuses your infisical login session. Run infisical login once if a
    wrapped command errors with an auth/credentials message.

Infisical folder scheme (1 level, no nesting)

  • / (root) — app-runtime secrets consumed by apps/web via cfg.get /
    process.env / import.meta.env.
  • /stripe — Stripe keys + pricing (STRIPE_*, *_PRICE_CENTS). The key set
    matches across dev and prod; only values differ (test vs live ids).
  • /infra — prod only, deploy/infra-only service config NOT consumed by
    apps/web at runtime (Caddy’s CLOUDFLARE_API_TOKEN + MCP_SQLITE_API_KEY,
    Phoenix server config PHOENIX_SECRET / PHOENIX_ENABLE_AUTH /
    PHOENIX_ENABLE_MCP_SERVER). Synced by the VPS deploy into service-specific
    env files. Do not create /infra in dev.

Intentional dev/prod parity gaps — do NOT “fix” these

These keys exist in one env only because they only make sense there. Forcing
parity breaks things:

Key In Why
R2_REGION, R2_FORCE_PATH_STYLE dev only dev points at local Garage; prod at real R2 (see above).
DATABASE_URL prod only There is no Turso today; the prod DB is a local libSQL/SQLite file (see data-model.md).
INTERNAL_BASE_URL prod only RunPod completion webhook callback; dev doesn’t dispatch to real RunPod.
PHOENIX_SECRET, PHOENIX_ENABLE_AUTH, PHOENIX_ENABLE_MCP_SERVER prod only Phoenix server config, read by deploy/phoenix/phoenix.env, not the app. Dev Phoenix runs without auth.
PHOENIX_API_KEY, PHOENIX_COLLECTOR_ENDPOINT prod only app-side OTel export target; dev defaults to http://localhost:6006 with no auth.
POSTHOG_API_KEY, POSTHOG_PERSONAL_API_KEY prod only optional; empty in dev = server-side analytics disabled locally.
RUNPOD_ENDPOINT_ID_SMALL_MUSIC, RUNPOD_ENDPOINT_ID_SMALL_SFX — endpoint ids; deliberately absent from the config-audit manifest while no job requests a small variant.
  • bun run build hard-fails on a missing required env var. The build
    script runs scripts/audit-build-env.ts first, which reuses the same
    CONFIG_KEYS manifest as the runtime audit and exits 1 (printing each gap
    and its impact) before vite build runs. The runtime audit stays non-fatal
    at boot on purpose (a downed server is its own incident); the build is where
    a missing secret turns into a failed deploy instead of a broken prod. Run it
    with dev secrets via mise run build:web
    (= cd apps/web && infisical run --env=dev -- bun run build).