---
title: Configuration & environment
description: Validate env at boot, understand production posture, and keep the app-facing KOVO variables straight.
order: 4.8
---

# Configuration & environment

Use this page when you are wiring a new app, moving from local PGlite to deployed Postgres, or
trying to answer "which env var does this surface actually read?" The smallest path is to validate
your own required env at boot and let the framework refuse early.

## Validate your env at boot

Start with the app-owned vars you actually need:

```ts
import { defineKovo, s } from '@kovojs/server';

const app = defineKovo({
  env: s.object({
    STRIPE_SECRET_KEY: s.string().pattern(/.+/),
    SENTRY_DSN: s.string().url().optional(),
  }),
});

export default app.assemble({});
```

That check happens at boot, not on the first live request. In production, invalid env blocks app
assembly. In development, the framework warns instead of bricking localhost.

## Run it

You can see the same failure mode with a missing framework secret by starting the app in production
without a real CSRF secret:

```sh
NODE_ENV=production pnpm dev
```

The boot path reports a typed assembly error instead of failing later from inside a route or
mutation.

## Understand the production shape

Boot mode comes from `NODE_ENV` unless an adapter overrides it. The practical differences are:

- Production refuses weak or missing framework secrets.
- Development warns about advisory issues instead of throwing.
- The default outbound egress floor is stricter in production.

The secret floor applies to the framework-owned signing material you wire through `defineKovo`, not
every random string in your process env.

## Use the app-facing variables

These are the env vars you will usually touch in app code, deploy config, or CI.

Every Postgres URL must explicitly name its login, database, and decimal port. Non-local runtime,
admin, and system URLs must also use a DNS hostname and exact `sslmode=verify-full`. Kovo validates
this before opening a connection and does not fill destination or identity fields from ambient
`PG*` variables. Only exact `127.0.0.1`, an exact query-host `::1`, and validated Unix-socket targets
may omit TLS for local development. Write the IPv6 control as
`postgres://app@localhost:5432/db?host=%3A%3A1`; Kovo rejects bracketed authority `[::1]` because
pinned `pg` passes those brackets to DNS. A process booted with `NODE_TLS_REJECT_UNAUTHORIZED=0`
cannot use a non-local managed Postgres URL.

| Variable                    | Used by                                                                       | What it does                                                                                |
| --------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `KOVO_CSRF_SECRET`          | `defineKovo({ csrf })`, starter auth                                          | Framework signing secret for browser mutation CSRF when you wire it through app config.     |
| `KOVO_DATABASE_URL`         | runtime DB, egress bootstrap                                                  | Ordinary least-privilege app login.                                                         |
| `KOVO_RUNTIME_DATABASE_URL` | `kovo db provision` / `kovo db migrate` / `kovo db check`                     | Runtime witness used for grants and posture. Usually the same login as `KOVO_DATABASE_URL`. |
| `KOVO_ADMIN_DATABASE_URL`   | `kovo db generate` / `kovo db migrate` / `kovo db provision` / fallback check | Privileged setup and fallback-check authority. Keep it out of the app process.              |
| `KOVO_DB_SYSTEM_URL`        | app boot, replay/auth storage, boot posture, `kovo db check`                  | Dedicated system login and preferred check authority on the same writable primary.          |
| `KOVO_DB_DRIVER`            | Postgres/PGlite runtime and `kovo db`                                         | Chooses `pglite`, `pg`, or `node-postgres`.                                                 |
| `KOVO_DATA_DIR`             | PGlite runtime and starter template                                           | Overrides the local PGlite directory.                                                       |
| `KOVO_DB_READER_ROLE`       | Postgres provision/check                                                      | Reader role name. Defaults to `kovo_reader`.                                                |
| `KOVO_DB_WRITER_ROLE`       | Postgres provision/check, durable tasks                                       | Writer role name. Defaults to `kovo_writer`.                                                |
| `KOVO_DB_ADMIN_ROLE`        | audited `crossOwnerRead(...)` posture                                         | Admin role for the narrower cross-owner read path.                                          |
| `KOVO_DB_SYSTEM_ROLE`       | framework replay/auth storage                                                 | System role name. Defaults to `kovo_system`.                                                |
| `KOVO_PRESET`               | `kovo build`                                                                  | Forces `node`, `vercel`, or `cloudflare` instead of host autodetection.                     |
| `KOVO_PARANOID`             | build/dev diagnostics                                                         | Turns on advisory extra security auditing.                                                  |
| `KOVO_SQL_GUARD`            | raw SQL migration escape hatch                                                | Temporary fail-open escape for unmanaged raw SQL sinks.                                     |
| `KOVO_DEVTOOL_BASE`         | devtool mount                                                                 | Prefixes emitted devtool URLs when you serve it under a subpath.                            |

Two more names matter even though they are not `KOVO_*`:

- `NODE_ENV` controls boot posture.
- `BETTER_AUTH_SECRET` is the common auth-layer secret the starter also accepts in place of
  `KOVO_CSRF_SECRET`.

## Handle failure

There are three common failure classes:

- Your app env schema rejects a missing or malformed app-owned variable.
- Production boot rejects a weak framework signing secret.
- The runtime and admin/system URLs resolve to different databases, clusters, or primary instances.

When that happens, fix the source env or the app wiring. Do not paper over it by catching the boot
error and continuing anyway.

## Next

- [Database lifecycle](/guides/database-lifecycle/) — wire the DB URLs and roles into real migrations.
- [Deployment](/guides/deployment/) — turn the same config into a production artifact.

<details>
<summary>Spec & diagnostics</summary>

Boot validation and `CreateAppBootError`: `packages/server/src/env.ts` and `packages/server/src/app.ts`.
DB/runtime env resolution: `packages/server/src/postgres-runtime.ts`, `packages/cli/src/commands/db.ts`,
and `packages/create-kovo/src/index.ts`. Build preset override and paranoid mode:
`packages/cli/src/commands/build-export.ts` and `packages/server/src/vite.ts`. CSP report endpoint:
`packages/server/src/csp.ts`. The main boot refusal path surfaces through `CreateAppBootError`; the
database posture family is the same KV433 set surfaced by `kovo db check`.

API reference: [@kovojs/server](/api/server/).

</details>
