# Environment and secrets

Keep Node, Worker and native configuration aligned without exposing credentials.

| Surface | Local source | Visibility |
| --- | --- | --- |
| Node scripts and web development | `apps/web/.env` | Server variables stay server-only |
| Local workerd | `apps/web/.dev.vars` and Wrangler bindings | Worker configuration |
| Expo | `apps/mobile/.env` | EXPO_PUBLIC_ values are bundled into the app |
| Production/preview | Worker secrets, bindings and workflow inputs | Server secrets must not become build-time public values |

`packages/env` validates each boundary separately. Web public configuration includes `VITE_SITE_URL`; native uses `EXPO_PUBLIC_SERVER_URL`. Never put credentials under public prefixes. Do not paste complete environment files into logs, issues or documentation.

## Database and storage [#database-and-storage]

Node uses `DATABASE_URL`. Production workerd uses Hyperdrive; its connection string belongs to the current invocation. Keep Hyperdrive caching disabled. Local built-Worker tests can override the Hyperdrive connection string with an isolated password-bearing local URL.

R2 credentials, bucket configuration and files gateway settings must describe the same environment. Public file URLs are an application concern; private objects still require authorization through the gateway.

## Runtime switches [#runtime-switches]

Four deployment switches are declared in `packages/env/src/runtime-flags.ts` and read on every call, never cached, so changing one on the Worker takes effect without a code change. Unset and empty mean the default. A value that does not parse never becomes a different setting: the reader keeps the safe behavior and logs `[env] <KEY>=<value> is invalid; …` once per isolate.

| Variable | Default | Accepted values | Behavior | Invalid value |
| --- | --- | --- | --- | --- |
| `DB_TENANT_PIPELINE` | pipelined | `0`, `1` | `0` is the kill switch: tenant statements fall back to explicit `BEGIN`/`COMMIT` ([runbook](/docs/development/runbooks#tenant-statements-fail-after-a-database-or-driver-change)). | Pipelining stays on, and the value is logged. |
| `TENANCY_PROBE` | off | `0`, `1` | `1` mounts `tenancy.probe` for the RLS preview proof. Previews set it; the production deploy refuses it. | The probe stays unmounted, and the value is logged. |
| `TRUSTED_PROXY_HEADER` | unset: the socket address is the client IP | a header name, such as `x-forwarded-for` | On Node behind your own proxy, the header the proxy appends the client to. Cloudflare Workers always use `cf-connecting-ip` and ignore it. | Proxy headers are ignored (the socket address is used), and the value is logged. |
| `TRUSTED_PROXIES` | `1` when `TRUSTED_PROXY_HEADER` is set | a positive integer | How many trusted proxies append to that header; the client is that many entries from the right. | The proxy header is ignored instead of falling back to one hop, and the value is logged. |

## Optional providers [#optional-providers]

Payments require provider selection and provider credentials, plus their visibility flags. AI requires provider configuration and `ai.assistant`. Email chooses an available transport in a defined order. A flag alone cannot configure an external account.

Run `pnpm setup` to populate or repair configuration and `pnpm doctor` to diagnose it. Keep environment examples descriptive; real values belong in ignored local files or the intended secrets store.


## Key files [#key-files]

- [`packages/env/src/server.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/packages/env/src/server.ts)
- [`packages/env/src/web.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/packages/env/src/web.ts)
- [`packages/env/src/runtime-flags.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/packages/env/src/runtime-flags.ts)
- [`packages/env/src/native.ts`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/packages/env/src/native.ts)
- [`apps/web/wrangler.jsonc`](https://github.com/CarlosZiegler/template-cloudflare/blob/main/apps/web/wrangler.jsonc)

## Select the runtime [#select-the-runtime]

<Tabs id="environment-runtime">
<Tab title="Node development">

```sh
# Reads apps/web/.env through the Node configuration path.
pnpm dev:web
```

Use the direct development database URL. This runtime does not emulate Worker sockets.

</Tab>
<Tab title="Built Worker">

```sh
pnpm --filter web build
pnpm --filter web exec wrangler dev -c dist/server/wrangler.json --local --port 8790
```

Supply local Worker variables and an isolated Hyperdrive connection override. This exercises the built workerd artifact.

</Tab>
</Tabs>

