# Configuration Reference

VibeXP is configured by a **single required `config.yaml`**. This page explains
that model and summarizes the **production-critical** values — it does not
duplicate every field (see
[Backend Configuration](/docs/developer-guide/backend/configuration/) for the full
list).

## The `config.yaml` model

At startup the backend loads one YAML file, resolved in this order:

1. the `--config <path>` flag,
2. the `VIBEXP_CONFIG_FILE` environment variable,
3. `./config.yaml` in the working directory.

The file is **required**: a missing file fails fast with a message naming the
expected path. String values support environment interpolation:

| Syntax | Meaning |
|---|---|
| `${VAR}` | The value of env var `VAR` (empty, with a warning, if unset). |
| `${VAR:-default}` | `VAR` when set and non-empty, otherwise `default`. |
| `$${literal}` | A literal `${literal}` (the `$$` escapes interpolation). |

Three files in the repo anchor the surface:

- [`backend/config.example.yaml`](https://github.com/vibexp/vibexp/blob/main/backend/config.example.yaml)
  — the complete, commented field list, tuned for local development. Copy it to
  author your own `config.yaml`.
- [`backend/config.schema.json`](https://github.com/vibexp/vibexp/blob/main/backend/config.schema.json)
  — a JSON Schema for editor validation of the YAML.
- [`backend/config.docker.yaml`](https://github.com/vibexp/vibexp/blob/main/backend/config.docker.yaml)
  — the production-neutral default **baked into the published image** at
  `/app/config.yaml` (with `VIBEXP_CONFIG_FILE` pointed at it). Every operator
  knob in it is a `${VAR:-default}` reference, so a container is configured with
  environment variables alone. To take full control, mount your own file over
  `/app/config.yaml`.

:::caution[Env vars only work when the file references them]
There is no generic environment override: an env var takes effect **only if the
loaded `config.yaml` references it as `${VAR}`**. The baked Docker config wires
the common knobs listed below; anything it does not reference (`auth.oauth_as.*` token TTLs and most tuning
fields) requires mounting your own `config.yaml`.
:::

## Must-set production values

These are the values (shown as the env vars wired in the baked image config)
you **must** review before exposing VibeXP to the internet. Defaults are for
local evaluation only.

| Env var | `config.yaml` key | Why it matters |
|---|---|---|
| `ENCRYPTION_KEY` | `security.encryption_key` | Required. **Exactly 32 bytes** (AES-256). Generate: `openssl rand -base64 24 \| cut -c1-32`. |
| `DB_HOST` / `DB_PORT` / `DB_USER` / `DB_PASSWORD` / `DB_NAME` | `database.*` | PostgreSQL connection. Change `DB_PASSWORD` from the default; keep it in sync with Postgres `POSTGRES_PASSWORD`. |
| `DB_SSLMODE` | `database.sslmode` | `disable` (default) or `require`. Set `require` for managed Postgres that mandates TLS. |
| `SESSION_ENCRYPTION_KEY` | `auth.session_encryption_key` | 64 hex chars (32 bytes) backing the session cookie and OAuth state HMAC. Generate: `openssl rand -hex 32`. |
| `FRONTEND_BASE_URL` | `frontend.base_url` | Your real public URL, e.g. `https://<your-app-host>` — the single origin serving both SPA and API. A non-localhost value automatically disables the dev-login bypass. |
| `OAUTH_AS_ISSUER_URL` | `auth.oauth_as.issuer_url` | Public **HTTPS** URL enabling the embedded OAuth 2.1 Authorization Server (MCP tokens). |
| `MCP_RESOURCE_URI` | `mcp.resource_uri` | Public MCP URL, the required token audience (RFC 8707), e.g. `https://<your-app-host>/mcp/v1/common`. |
| `INSTANCE_ADMIN_EMAILS` | `auth.instance_admins` | Comma-separated emails of the **root** instance admins: they get the admin portal, finish first-run setup, and are the only ones who can grant the role to others ([Instance Admin Portal](/docs/user-guide/self-hosting/instance-admin/)). Set at least one before the first start. Since v0.15.0 an instance admin is also required to configure the instance's outbound mail ([Instance Email](/docs/user-guide/self-hosting/instance-email/)), so with this empty and no mail configured nobody can set it up. |
| `AUTH_SETTINGS_RECOVERY_MODE` | `auth.recovery_mode` | Leave unset. Set to `true` only to get back in after an authentication lockout: setup mode is forced on and every start logs a new one-time `SETUP URL`, while the configured providers keep working. Remove it once sign-in works again. See [Lockout Recovery](/docs/user-guide/self-hosting/lockout-recovery/). |
| `TRUSTED_PROXIES` | `server.trusted_proxies` | Set if you run behind a reverse proxy or load balancer. Comma-separated CIDRs allowed to assert a client IP via `X-Forwarded-For` / `X-Real-IP` (e.g. `10.0.0.0/8,172.16.0.0/12`; `192.168.1.5/32` for one host). Empty ignores those headers and uses the connecting peer, correct for a directly exposed instance. Left empty behind a proxy, per-IP rate limits collapse into one bucket and logs show the proxy address; the backend warns at startup. An invalid CIDR fails startup. |
| `OUTBOUND_ALLOWED_CIDRS` | `security.outbound_allowed_cidrs` | Comma-separated CIDRs the SSRF guard may dial even though they are loopback or private. Empty (the default) refuses every reserved range, which is what blocks a self-hosted embedding or model sidecar on a private Docker subnet. Declare only the network your own service runs on (e.g. `172.16.0.0/12`, or `127.0.0.1/32` for a same-host Ollama). Link-local (`169.254.0.0/16`, `fe80::/10`, i.e. cloud metadata) and multicast (`224.0.0.0/4`, `ff00::/8`) can never be allowlisted; a malformed CIDR or one overlapping those ranges fails startup. Local development is exempt: a localhost `FRONTEND_BASE_URL` already permits reserved ranges. |

:::note[Sign-in providers are not in this table]
Since v0.15.0 the sign-in providers (Google, GitHub, OIDC) and the access
allowlist are stored in the database and managed in the admin area under
**Settings** → **Authentication**, not set through environment variables. A new
instance logs a one-time `SETUP URL` at startup for adding the first provider:
see [Authentication & MCP Auth](/docs/user-guide/self-hosting/authentication/#first-run-setup).
The variables earlier releases used for this are deprecated and only imported
at startup while nothing is stored: see the
[upgrade note](/docs/user-guide/self-hosting/upgrading/#sign-in-providers-and-the-access-allowlist-moved-to-the-database-v0150).
:::

:::note[Native-CLI login on REST auto-wires]
Enabling the embedded AS (`OAUTH_AS_ISSUER_URL`) is enough for the native CLI's
browser login (`vibexp auth login`) to work against the REST API (`/api/v1`).
`/api/v1` JWT acceptance auto-derives from the AS, so you do **not** need to set
`API_OAUTH_ISSUER` for this. The image also has no audiences env var, audiences
auto-derive from `MCP_RESOURCE_URI`. Set `API_OAUTH_ISSUER` (issuer only) only
to point `/api/v1` at an external IdP instead.
:::

:::caution
Changing `DB_PASSWORD` after the first run does not retroactively change an
already-initialized database. Set it before the first `docker compose up`, or
reset the data volume (`docker compose down -v`) if you change it later.
:::

## Frontend runtime configuration

The SPA is embedded in the backend and served same-origin, so there is no
frontend deployment to configure. The backend serves runtime configuration at
**`/config.js`** (as `window.__VIBEXP_ENV__`), rendered from the `frontend.*`
config keys (`VITE_MCP_ENDPOINT`, GTM/GA4 analytics, …) — change one with an
env var and a restart, no rebuild. Never put secrets there: `/config.js` is
world-readable.

Company branding (name, logo, support and legal links) is not an env var. An
instance admin sets it in the admin area under **Settings** → **Site info**:
see [Site Info (Branding)](/docs/user-guide/self-hosting/site-info/).

The only frontend **build**-time values are deployment-neutral: the relative
`VITE_API_BASE_URL=/api/v1` and the `VITE_RELEASE_SHA` / `VITE_RELEASE_DATE`
release stamps.

→ Full list: [Frontend Configuration](/docs/developer-guide/frontend/configuration/).

## Backend fields

The backend has many more fields than the production-critical subset above
(rate limits, retention, telemetry, …). They
live in `config.yaml`; `config.example.yaml` documents every one.

Search ranking and AI summary defaults are no longer among them. Since v0.15.0
an instance admin sets them in the admin area, under Settings → Search and
Settings → AI Summary (see
[Search & AI Summary Settings](/docs/user-guide/self-hosting/search-and-ai-summary-settings/)).
The `search:` and `ai_summary:` sections are deprecated and only imported at
startup (see
[Upgrading](/docs/user-guide/self-hosting/upgrading/#search-ranking-and-ai-summary-defaults-moved-to-admin--settings-v0150)).

Instance email is no longer one of them. Since v0.15.0 an instance admin
configures it in the admin area, under Settings → Email (see
[Instance Email](/docs/user-guide/self-hosting/instance-email/)). The `email:`
section and its environment variables (`EMAIL_PROVIDER`, `SMTP_*`, …) are
deprecated: they are imported at startup while nothing is stored and ignored
once a provider is, as described in the
[upgrade note](/docs/user-guide/self-hosting/upgrading/#instance-email-moved-to-admin--settings--email-v0150).

→ Full list: [Backend Configuration](/docs/developer-guide/backend/configuration/).

## Optional services

- **Semantic search** — embeddings run in-process; vector width is fixed at
  **1024**. Embedding providers are configured **per team in the app** (model,
  chunk sizing, concurrency, and query/document prefixes included), not via
  environment.
- **Model providers**: per-team OpenAI-compatible LLM endpoints, also managed
  in the app with encrypted API keys.
- **File attachments** — enable the GCS emulator service and the related `app`
  variables. See [Docker & Compose](/docs/developer-guide/deployment/docker/).
- **Scheduler**: the in-process loop for recurring work; today it runs
  freshness-rule evaluation. On by default. Set `SCHEDULER_ENABLED=false` to
  turn it off (freshness evaluation stops with it), and tune it with
  `SCHEDULER_TICK_INTERVAL` / `SCHEDULER_JOB_TIMEOUT` / `SCHEDULER_DUE_LIMIT`.
  See [Backend Configuration](/docs/developer-guide/backend/configuration/#scheduler).

Every running instance serves its own API spec at `/openapi.yaml` and
`/openapi.json`.

## Related

- [Self-Hosting](/docs/developer-guide/deployment/self-hosting/)
- [Docker & Compose](/docs/developer-guide/deployment/docker/)