> ## Documentation Index
> Fetch the complete documentation index at: https://getoverlay.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-Hosting

> Run Overlay Web outside the hosted SaaS environment.

This guide covers the current runtime configuration path for running Overlay outside the default Vercel, WorkOS, Stripe, and Cloudflare R2 SaaS setup.

For enterprise IT teams that want source visibility and customization, use [Enterprise Source Install](/deploy-operate/enterprise-source-install):

```bash theme={null}
git clone https://github.com/layernorm/overlay-web.git
cd overlay-web
cp .env.enterprise.example .env
cp docs/config/enterprise-private.example.json overlay.config.json
docker compose pull
docker compose up -d
```

That path runs the official image by default while keeping the full source checkout available for audit, configuration, and source-level customization.

For the simplest pilot/operator path without a source checkout, use the one-command installer:

```bash theme={null}
curl -fsSL https://getoverlay.io/install.sh | bash
```

That installer creates `/opt/overlay/overlay.config.json`, `/opt/overlay/.env`, `/opt/overlay/docker-compose.yml`, and an `overlayctl` manager. See [One-Command Install](/deploy-operate/one-command-install) for install options, commands, and common config edits.

For tenant boundaries and enterprise role modeling, see [Tenancy And Role Model](/deploy-operate/tenancy). The short version: self-hosted and managed-cloud enterprise deployments are single-customer deployments; students, teachers, parents, admins, departments, and classes are roles or groups inside that deployment, not tenants.

For licensing and brand constraints, see [Licensing](/legal/licensing) and [Legal Self-Hosting Notes](/legal/self-hosting-obligations). The short version: core product code is `AGPL-3.0-or-later`, reusable SDK/contract packages are `Apache-2.0`, and modified distributions need their own branding unless LayerNorm Inc. gives written permission.

The app now loads configuration from three layers, highest precedence first:

1. Environment variables.
2. `OVERLAY_CONFIG_FILE`, normally `overlay.config.json`.
3. The default config in `src/shared/config/defaultOverlayRuntimeConfig.ts`.

Use the examples in [Deployment Profiles](#deployment-profiles) as starting points. They are validated by `npm run docs:health`.

## Deployment Models

| Model                        | Operator  | Tenant boundary                       | Best for                                                             |
| ---------------------------- | --------- | ------------------------------------- | -------------------------------------------------------------------- |
| **Hosted SaaS**              | LayerNorm | LayerNorm's shared SaaS environment   | Teams that want zero infrastructure work.                            |
| **Managed Cloud Enterprise** | LayerNorm | One dedicated deployment per customer | Enterprises that need isolation without self-hosting.                |
| **Self-Hosted / On-Prem**    | Customer  | One deployment per customer           | Organizations that need full source control and in-house operations. |

See [Managed Cloud Enterprise](./cloud-managed) for the managed-cloud option and [Tenancy And Role Model](./tenancy) for the tenant boundary.

## Required Environment Variables

Minimum app runtime variables:

| Variable                        | Required when                     | Notes                                                                                                                                        |
| ------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `OVERLAY_CONFIG_FILE`           | Using a JSON runtime config       | Absolute or repo-relative path, for example `overlay.config.json`.                                                                           |
| `NEXT_PUBLIC_APP_URL`           | Always in deployed environments   | Public browser URL for the web app.                                                                                                          |
| `NEXT_PUBLIC_CONVEX_URL`        | Browser or server talks to Convex | Public Convex deployment URL for the selected environment. Use `DEV_NEXT_PUBLIC_CONVEX_URL` for local dev against a separate dev deployment. |
| `INTERNAL_API_SECRET`           | Always                            | Shared server-to-Convex secret. The same value must be configured in the matching Convex deployment.                                         |
| `INTERNAL_SERVICE_AUTH_SECRET`  | App runtime only                  | Used for internal app service auth. Do not put this in Convex.                                                                               |
| `SESSION_SECRET`                | Cookie sessions                   | App runtime only. Must be long, random, and environment-specific.                                                                            |
| `SESSION_TRANSFER_KEY`          | Native/session transfer flows     | App runtime only.                                                                                                                            |
| `SESSION_COOKIE_ENCRYPTION_KEY` | Encrypted session cookie payloads | App runtime only.                                                                                                                            |
| `API_KEY_HASH_SECRET`           | `capabilities.apiKeys=true`       | App runtime only. HMAC pepper for API key hashes.                                                                                            |

Provider-specific variables:

| Variable                                                                                                                                                            | Provider                                      | Notes                                                                                                                                                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `WORKOS_CLIENT_ID`, `WORKOS_API_KEY`                                                                                                                                | `auth.provider="workos"`                      | Use production WorkOS values only with production app and Convex deployments.                                                                                                                                                  |
| `DEV_WORKOS_CLIENT_ID`, `DEV_WORKOS_API_KEY`                                                                                                                        | Local or staging WorkOS fallback              | Only use when `auth.allowDevFallbacks=true` in non-production.                                                                                                                                                                 |
| `BETTER_AUTH_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_DATABASE_URL`, `BETTER_AUTH_DEFAULT_SSO_*`, `BETTER_AUTH_OIDC_*`, `BETTER_AUTH_JWT_*`, `BETTER_AUTH_JWKS_URL` | `auth.provider="better-auth"`                 | Better Auth owns the web session in Overlay and issues short-lived JWTs for Convex verification through its JWKS endpoint. Keep this database auth-scoped; it does not replace Convex app data.                                |
| `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_AUDIENCE`                                                                                          | `auth.provider="oidc"`                        | Verifies RS256 bearer/access tokens against OIDC discovery JWKS and maps standard profile claims. Use these variables for Keycloak-compatible realm issuers too. Browser sign-in/callback wiring is still deployment-specific. |
| `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`                                                                                                                        | `billing.provider="stripe"`                   | Required only when billing is enabled.                                                                                                                                                                                         |
| `DEV_STRIPE_SECRET_KEY`, `DEV_STRIPE_WEBHOOK_SECRET`                                                                                                                | Staging Stripe                                | Preferred for non-production.                                                                                                                                                                                                  |
| `STRIPE_PAID_UNIT_PRICE_ID`, `STRIPE_TOPUP_UNIT_PRICE_ID`, `STRIPE_PORTAL_CONFIGURATION_ID`                                                                         | Stripe billing UI                             | Use matching test or live mode IDs for the selected environment.                                                                                                                                                               |
| `R2_ACCOUNT_ID`, `R2_BUCKET_NAME`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `S3_API`                                                                             | `storage.provider="r2"`                       | `S3_API` is the R2 S3-compatible endpoint.                                                                                                                                                                                     |
| `S3_BUCKET_NAME`, `S3_REGION`, `S3_ENDPOINT_URL`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_FORCE_PATH_STYLE`                                                 | `storage.provider="s3"`                       | Use `S3_ENDPOINT_URL` and `S3_FORCE_PATH_STYLE=true` for S3-compatible enterprise storage that needs path-style access.                                                                                                        |
| `OPENROUTER_API_KEY`                                                                                                                                                | `llm.gatewayProvider="openrouter"`            | Default SaaS-style model gateway.                                                                                                                                                                                              |
| `AI_GATEWAY_API_KEY`                                                                                                                                                | `llm.gatewayProvider="ai-gateway"`            | Vercel AI Gateway compatible path.                                                                                                                                                                                             |
| `OPENAI_API_KEY`                                                                                                                                                    | `llm.gatewayProvider="openai"`                | Used by the active OpenAI provider adapter.                                                                                                                                                                                    |
| `ANTHROPIC_API_KEY`, `GROQ_API_KEY`                                                                                                                                 | `llm.gatewayProvider="anthropic"` or `"groq"` | Used by the direct Anthropic and Groq provider adapters unless `LLM_API_KEY_ENV_VAR` points at a different secret name.                                                                                                        |

Enterprise runtime config v2 overrides:

| Variable                                                                                                                                                   | Notes                                                                                                                                                                                            |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OVERLAY_CONFIG_VERSION=2`                                                                                                                                 | Selects the enterprise config v2 shape. The parser also normalizes existing v1-shaped files into v2 internally.                                                                                  |
| `OVERLAY_CONFIG_PRESET`                                                                                                                                    | One of `saas-default`, `onprem-minimal`, `enterprise-private`, or `dpdp-strict`.                                                                                                                 |
| `OVERLAY_COMPLIANCE_PROFILE`                                                                                                                               | One of `saas-default`, `onprem-minimal`, `enterprise-private`, `dpdp-strict`, or `custom`.                                                                                                       |
| `OVERLAY_MINOR_MODE`                                                                                                                                       | Enables minor-oriented compliance mode metadata.                                                                                                                                                 |
| `OVERLAY_ALLOW_EXTERNAL_PROCESSORS`                                                                                                                        | Set `false` to require risky processors to be disabled or explicitly allowlisted in strict profiles.                                                                                             |
| `OVERLAY_ALLOWED_PROCESSORS`                                                                                                                               | Comma-separated allowlist such as `models:openai,objectStorage:s3`.                                                                                                                              |
| `OVERLAY_ALLOWED_REGIONS`                                                                                                                                  | Comma-separated region/residency list.                                                                                                                                                           |
| `OVERLAY_FEATURE_BROWSER_USE`, `OVERLAY_FEATURE_SANDBOXES`, `OVERLAY_FEATURE_INTEGRATIONS`, `OVERLAY_FEATURE_WEB_SEARCH`                                   | Server/UI gates for high-risk processor surfaces. Use `false` to disable.                                                                                                                        |
| `OVERLAY_FEATURE_ANALYTICS`, `OVERLAY_FEATURE_ERROR_REPORTING`                                                                                             | Runtime gates for analytics and error reporting. For client-side pre-init gating, also set `NEXT_PUBLIC_OVERLAY_FEATURE_ANALYTICS=false` or `NEXT_PUBLIC_OVERLAY_FEATURE_ERROR_REPORTING=false`. |
| `OVERLAY_PROVIDER_SANDBOX`, `OVERLAY_PROVIDER_BROWSER`, `OVERLAY_PROVIDER_WEB_SEARCH`, `OVERLAY_PROVIDER_ANALYTICS`, `OVERLAY_PROVIDER_ERROR_REPORTING`    | Provider selectors. Use `none` to hard-disable a provider family.                                                                                                                                |
| `OVERLAY_PROVIDER_DATABASE`, `OVERLAY_PROVIDER_VECTOR_SEARCH`, `OVERLAY_PROVIDER_OBJECT_STORAGE`, `OVERLAY_PROVIDER_MODELS`, `OVERLAY_PROVIDER_RATE_LIMIT` | Enterprise provider selectors. Unsupported declared providers fail during config validation with an explicit error.                                                                              |

Optional observability/integration variables:

| Variable                                                    | Notes                                                                               |
| ----------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_POSTHOG_TOKEN`, `NEXT_PUBLIC_POSTHOG_HOST`     | Public analytics config. Safe to expose, but use separate projects per environment. |
| `SENTRY_AUTH_TOKEN`, `SENTRY_DSN`, `NEXT_PUBLIC_SENTRY_DSN` | Use separate Sentry projects for staging and production.                            |
| `PROVIDER_KEYS_SECRET`, `HOOKS_TOKEN_SALT`                  | App runtime only, used by integration/provider key flows.                           |

## Configure overlay.config.json

Create `overlay.config.json` at the repo root or point `OVERLAY_CONFIG_FILE` to another path:

```bash theme={null}
cp docs/config/onprem-minimal.example.json overlay.config.json
OVERLAY_CONFIG_FILE=overlay.config.json npm run dev
```

The normalized shape is:

| Section        | Purpose                                                                           |
| -------------- | --------------------------------------------------------------------------------- |
| `app`          | Base URL, deployment environment, CSP connect-src additions, and public env keys. |
| `auth`         | WorkOS, OIDC, or no-op auth selection.                                            |
| `billing`      | Stripe or disabled billing.                                                       |
| `storage`      | R2, S3-compatible storage, or no-op object store.                                 |
| `llm`          | Gateway provider, provider key source, default model, and allowlist.              |
| `database`     | Convex URL/deployment and internal server secrets.                                |
| `capabilities` | Feature booleans consumed by browser UI and server route guards.                  |

Enterprise config v2 adds these top-level sections while keeping all keys above backward compatible:

| Section         | Purpose                                                                                                                                                                                                                             |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `configVersion` | Must be `2` for explicit v2 config files. Existing files without it still parse.                                                                                                                                                    |
| `preset`        | Deployment preset: `saas-default`, `onprem-minimal`, `enterprise-private`, or `dpdp-strict`.                                                                                                                                        |
| `compliance`    | Compliance profile, minor mode, external processor policy, allowed processors/regions, and retention defaults.                                                                                                                      |
| `features`      | Boolean gates for chat, files, memory, knowledge, automations, integrations, browser use, sandboxes, web search, analytics, error reporting, billing, webhooks, API keys, vector search, model routing, SSO, and multi-tenant mode. |
| `providers`     | Grouped provider selections for auth, database, object storage, vector search, embeddings, models, integrations, browser, sandbox, web search, analytics, error reporting, secrets, and rate limits.                                |

Environment variables override JSON values. Use that for secrets in deployed environments. For example, keep `storage.s3.bucketName` in JSON but inject `S3_SECRET_ACCESS_KEY` from your runtime secret manager.

Validate a config before booting the app:

```bash theme={null}
OVERLAY_CONFIG_FILE=overlay.config.json npm run check:config
npm run docs:health
```

Before an on-prem release, run the complete enterprise release gate:

```bash theme={null}
npm run check:phase6
```

The manual UI checklist is in [Release Gates](/deploy-operate/release-gates).

## Deployment Profiles

Validated examples live under `docs/config`:

| Profile                | File                                                                               | Use case                                                                                                 |
| ---------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| SaaS staging           | [saas-staging.example.json](../config/saas-staging.example.json)                   | WorkOS, Stripe test mode, R2, OpenRouter, API key infrastructure enabled.                                |
| On-prem minimal        | [onprem-minimal.example.json](../config/onprem-minimal.example.json)               | No WorkOS, no Stripe, S3-compatible storage, no external LLM key. Good for shell and config smoke tests. |
| On-prem S3/OIDC/OpenAI | [onprem-s3-oidc-openai.example.json](../config/onprem-s3-oidc-openai.example.json) | Enterprise OIDC, S3-compatible storage, no billing, OpenAI model gateway.                                |
| Enterprise private     | [enterprise-private.example.json](../config/enterprise-private.example.json)       | OIDC, S3, direct OpenAI model provider, no billing, no browser/sandbox/integrations/search/telemetry.    |
| DPDP strict            | [dpdp-strict.example.json](../config/dpdp-strict.example.json)                     | Minor mode metadata, external processors disabled by default, no model routing/search/tools/telemetry.   |

The examples contain placeholders only. Replace every `placeholder` or `replace_with` value before a real deployment.

## Provider Swaps

Auth providers:

| Provider      | Config            | Current status                                                                                                                                                            |
| ------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `workos`      | `auth.workos`     | Default SaaS path. Production-ready path for current hosted app.                                                                                                          |
| `better-auth` | `auth.betterAuth` | Better Auth web sessions plus SSO and JWKS-backed service JWTs for Convex. Requires an auth-scoped Postgres database and Better Auth migrations before production use.    |
| `oidc`        | `auth.oidc`       | Runtime-config OIDC bearer/access-token verification, including Keycloak-compatible realm issuers. Verify browser sign-in/callback wiring for your IdP before production. |
| `none`        | none              | Local/on-prem no-op auth. Pair with `capabilities.sso=false`.                                                                                                             |

Billing providers:

| Provider | Config           | Current status                                                                                      |
| -------- | ---------------- | --------------------------------------------------------------------------------------------------- |
| `stripe` | `billing.stripe` | SaaS checkout, portal, top-ups, webhooks. Pair with `capabilities.billing=true`.                    |
| `none`   | none             | No-op billing. Pair with `capabilities.billing=false`; billing UI and routes are hidden or blocked. |

Storage providers:

| Provider | Config       | Current status                                                                |
| -------- | ------------ | ----------------------------------------------------------------------------- |
| `r2`     | `storage.r2` | Cloudflare R2. Default SaaS storage.                                          |
| `s3`     | `storage.s3` | S3-compatible object store. Use for AWS S3 or compatible enterprise stores.   |
| `none`   | none         | No-op object store. Use only when upload flows are intentionally unavailable. |

LLM providers:

| Provider            | Config                                      | Current status                                                                                                                        |
| ------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `openrouter`        | `llm.gatewayProvider`, `OPENROUTER_API_KEY` | Default SaaS gateway path.                                                                                                            |
| `ai-gateway`        | `llm.gatewayProvider`, `AI_GATEWAY_API_KEY` | Vercel AI Gateway compatible path.                                                                                                    |
| `openai`            | `llm.gatewayProvider`, `OPENAI_API_KEY`     | Active OpenAI adapter.                                                                                                                |
| `anthropic`, `groq` | provider name plus provider API key env     | Active direct provider adapters. Use `ANTHROPIC_API_KEY` or `GROQ_API_KEY`, or set `LLM_API_KEY_ENV_VAR` to your runtime secret name. |
| `none`              | none                                        | No-op LLM provider for shell/config QA.                                                                                               |

Enterprise provider matrix:

| Purpose         | Provider values                                                   | Data processed                                                | Region/residency                                                             | Current support                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| --------------- | ----------------------------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Auth            | `workos`, `oidc`, `none`                                          | Identity profile, session tokens, org/user metadata           | Depends on IdP                                                               | Supported. OIDC verifies bearer/access tokens through discovery JWKS, including Keycloak-compatible realm issuers; browser login wiring is deployment-specific.                                                                                                                                                                                                                                                                                     |
| Database        | `convex`, `postgres`                                              | App records, messages, files metadata, usage state            | Convex deployment region today, or customer Postgres region in Postgres mode | `convex` supported. `postgres` supports auth-linked users, chat, projects, notes, files and outputs, account deletion, durable maintenance, memory extraction, pgvector retrieval, scheduled automations, webhooks, usage/billing records, scoped API keys, administrative audit, skills, and MCP server persistence/execution audits. Integration connection state remains in the selected integration provider rather than the app-data database. |
| Object storage  | `r2`, `s3`, `none`                                                | Uploaded files and generated artifacts                        | Bucket/provider region                                                       | Supported for Cloudflare R2 and S3-compatible enterprise object stores.                                                                                                                                                                                                                                                                                                                                                                             |
| Vector search   | `convex`, `pgvector`, `pinecone`, `none`                          | Embeddings and searchable text chunks                         | Database/vector provider region                                              | `convex`, `pgvector`, and `none` are supported. `pgvector` requires Postgres, the `vector` extension, an embeddings provider, migrations, and the durable worker. `pinecone` is declared but rejected until an adapter exists.                                                                                                                                                                                                                      |
| Embeddings      | `ai-gateway`, `openai`, `azure-openai`, `none`                    | Text chunks sent for embedding                                | Provider region/policy                                                       | `ai-gateway`, `openai`, and `none` declared for current wiring. `azure-openai` is rejected until an adapter exists.                                                                                                                                                                                                                                                                                                                                 |
| Models          | `openrouter`, `ai-gateway`, `openai`, `anthropic`, `groq`, `none` | User prompts, context, tool results                           | Model provider policy                                                        | Supported. Use `features.modelRouting=false` plus `providers.models.provider="none"` to disable.                                                                                                                                                                                                                                                                                                                                                    |
| Integrations    | `composio`, `executor`, `mcp`, `none`                             | Connected-account metadata and tool inputs/outputs            | Provider/account region                                                      | `composio`, `executor`, and `none` supported. `executor` uses a separate customer-hosted service and provider-managed connections. `mcp` as a provider family is declared but rejected until bootstrap wiring exists.                                                                                                                                                                                                                               |
| Browser use     | `browser-use`, `self-hosted-playwright`, `none`                   | Browser tasks, page content, credentials entered during tasks | Browser provider region                                                      | `browser-use` and `none` supported. `self-hosted-playwright` is declared but rejected until an adapter exists.                                                                                                                                                                                                                                                                                                                                      |
| Sandboxes       | `daytona`, `e2b`, `local-firecracker`, `none`                     | Commands, staged files, generated artifacts                   | Sandbox provider region                                                      | `daytona` and `none` supported. `e2b` and `local-firecracker` are declared but rejected until adapters exist.                                                                                                                                                                                                                                                                                                                                       |
| Web search      | `ai-gateway`, `perplexity`, `tavily`, `none`                      | Search queries and result snippets                            | Search/model provider policy                                                 | `ai-gateway` and `none` supported. Direct `perplexity` and `tavily` are declared but rejected until adapters exist.                                                                                                                                                                                                                                                                                                                                 |
| Analytics       | `posthog`, `none`                                                 | Pageviews and product events                                  | PostHog project region                                                       | Supported. Set analytics feature/provider to `false`/`none` to avoid SDK initialization and capture.                                                                                                                                                                                                                                                                                                                                                |
| Error reporting | `sentry`, `none`                                                  | Error events and sanitized stack/request metadata             | Sentry project region                                                        | Supported. Set error reporting feature/provider to `false`/`none` to avoid initialization and capture.                                                                                                                                                                                                                                                                                                                                              |
| Secrets         | `env`, `workos-vault`, `aws-secrets-manager`, `vault`, `none`     | Provider credentials                                          | Secret manager region                                                        | `env` supported; WorkOS vault-backed provider keys exist for selected flows. AWS Secrets Manager and Vault are declared but rejected for runtime secret loading.                                                                                                                                                                                                                                                                                    |
| Rate limit      | `convex`, `memory`, `redis`, `none`                               | Rate-limit counters                                           | App/database region                                                          | All declared providers are supported. Use Redis with fail-closed behavior for multi-instance production/on-prem deployments; memory is development-only.                                                                                                                                                                                                                                                                                            |

### Postgres app-data setup

For MCP servers, generate a separate credential-encryption key:

```bash theme={null}
openssl rand -hex 32
```

Store it as `MCP_CREDENTIAL_ENCRYPTION_KEY` on the web and worker runtimes. During
rotation, place the old value in `MCP_CREDENTIAL_ENCRYPTION_KEY_PREVIOUS`, deploy
the new primary key, rewrite saved credentials through the management API, then
remove the previous key. MCP responses expose only `hasAuth`; bearer tokens and
custom header values are AES-256-GCM encrypted at rest and are never returned by
list APIs. Production MCP URLs must use HTTPS and pass Overlay's SSRF validation.

Each server has a default execution policy of `allow`, `approval_required`, or
`deny`, plus optional per-tool overrides. Use `approval_required` as the enterprise
default when a server contains write-capable tools. Tool name, argument hash,
policy decision, result status, duration, and request context are persisted for
audit without storing raw tool arguments.

Postgres app-data support is split into phases. Operators can provision, validate, migrate, and smoke-test the app-data schema now. Run with `billing.provider="none"` and keep integrations and API keys disabled until their parity phases land. Projects, files, generated media, browser outputs, Daytona artifacts, saved memories, knowledge retrieval, scheduled automations, and signed outbound webhooks are supported.

Postgres-native memory and knowledge search uses `pgvector`; do not keep Convex enabled in the browser as a hidden vector sidecar. Confirm `vector` is available before migration, then set:

```env theme={null}
OVERLAY_PROVIDER_VECTOR_SEARCH=pgvector
OVERLAY_PROVIDER_EMBEDDINGS=openai
OVERLAY_EMBEDDING_MODEL_VERSION=text-embedding-3-small-v1
OVERLAY_MEMORY_EXTRACTION_MODEL_ID=gpt-4.1-mini
OVERLAY_RETENTION_MEMORY_DAYS=365
VECTOR_SEARCH_ENABLED=true
OVERLAY_FEATURE_VECTOR_SEARCH=true
```

Run the durable worker continuously. It executes memory extraction, indexing,
failed-index recovery, retention, and embedding-model backfills:

```bash theme={null}
npm run app-db:worker
```

When Postgres app-data uses automations or outbound webhooks, declare the
supervised runtime explicitly in the web, worker, and scheduler environments:

```bash theme={null}
OVERLAY_BACKGROUND_RUNTIME_ENABLED=true
INTERNAL_SERVICE_AUTH_SECRET="replace-with-a-strong-distinct-secret"
```

The Postgres capability response hides automations and webhooks until the
background runtime is declared. Automations additionally require
`INTERNAL_SERVICE_AUTH_SECRET`. Set the declaration only when at least one
scheduler and one worker are continuously supervised.

When changing `OVERLAY_EMBEDDING_MODEL_VERSION`, deploy the new worker and run:

```bash theme={null}
npm run app-db:knowledge-backfill -- --limit=2000
```

Repeat until `queued` is zero, then allow workers to drain the queue before
retiring the old embedding model configuration.

For local setup:

```bash theme={null}
cp .env.app-data.local.example .env.app-data.local
npm run app-db:up
npm run app-db:migrate
npm run app-db:smoke
npm run app-db:worker:smoke
```

For cloud Postgres, set `OVERLAY_DATABASE_URL` and run:

```bash theme={null}
OVERLAY_DATABASE_URL="postgres://..." npm run app-db:migrate
OVERLAY_DATABASE_URL="postgres://..." npm run app-db:smoke
OVERLAY_DATABASE_URL="postgres://..." npm run app-db:worker:smoke
```

Do not reuse `BETTER_AUTH_DATABASE_URL` for app data. Better Auth and Overlay app records have separate schemas and migration lifecycles.

### Billing, API keys, and administrators

Postgres app-data supports Stripe subscription records, exactly-once top-up grants,
and durable Stripe event deduplication. Register the Postgres deployment webhook as:

```text theme={null}
https://overlay.example.com/api/webhooks/stripe
```

Set `STRIPE_WEBHOOK_SECRET` to that endpoint's signing secret. Convex deployments
continue to use the Convex Stripe webhook endpoint; do not register both endpoints
for one deployment.

API key values are returned only on create or rotation. The database stores an
HMAC hash using `API_KEY_HASH_SECRET`, plus scopes, expiry, use metadata, and
revocation state. Rotate the hash secret only through a planned key invalidation
window because existing API keys cannot be re-hashed without their plaintext.

Bootstrap the first administrator from a trusted maintenance shell after the user
has signed in once and exists in the app-data `users` table:

```bash theme={null}
OVERLAY_DATABASE_URL="postgres://..." npm run app-db:admin -- \
  grant --user-id USER_ID --role admin --reason "initial operator"
```

Then use `/api/v1/admin/principals` for role grants/revocations and
`/api/v1/admin/audit` for audit review. `/app/admin` exposes usage filters,
institutional budget adjustment, and audit search; `/api/v1/admin/usage` is its
provider-neutral API. Available roles are `admin`, `auditor`,
`billing_admin`, and `support`. Only `admin` can grant or revoke roles, auditors
can read audit records, and an administrator cannot revoke their own active role.
Audit metadata is size-bounded and secret-shaped keys are redacted before insert.
Users manage scoped credentials under Account settings. Before an AWS pilot,
complete the [AWS deployment](/deploy-operate/aws),
[security](/deploy-operate/security), and
[release gate](/deploy-operate/release-gates) procedures for each enabled
billing profile.

For future vector-search planning, confirm that the managed Postgres provider exposes the `vector` extension:

```bash theme={null}
OVERLAY_DATABASE_URL="postgres://..." npm run app-db:vector-readiness
```

This readiness command is read-only. It does not enable vector search, install extensions, create vector columns, or change the app-data schema.

Postgres app-data does not use Convex crons. Run `npm run app-db:worker` as a separately supervised service and set `OVERLAY_BACKGROUND_RUNTIME_ENABLED=true` on every release component. Its Postgres scheduler enqueues maintenance, coordination cleanup, model-catalog refresh, S3 reconciliation, output retention, knowledge maintenance, due automation scans, and Daytona reconciliation. Leased workers execute jobs with retry, lease recovery, and dead-letter state. Automation occurrences are idempotent, each attempt is audited, overlapping runs obey the configured `skip` or `queue` policy, and cancellation is checked before execution. Outbound webhook events are deduplicated per subscription/event, signed over `timestamp.payload` with HMAC-SHA256, and retain delivery-attempt and dead-letter records. Signing secrets are returned only when a subscription is created or rotated; dead-letter redrive creates a new immutable delivery record. Daytona reconciliation adopts only correctly labeled workspaces, rejects unowned provider records, marks missing workspaces, and advances active metering windows idempotently. The standalone `npm run app-db:maintenance` command remains available for manual recovery. `compliance.retention.fileDays` governs generated media/browser outputs and `compliance.retention.sandboxArtifactDays` governs Daytona artifacts.

Postgres chat realtime uses the `conversation_events` table and `/api/v1/conversations/events` long polling. Transactional `pg_notify` wakes one listener connection per web process; notifications are only an optimization because reconnects read from the durable cursor. It does not require sticky load-balancer sessions. Keep the worker running so expired event rows and message deltas are pruned. Each app tab holds at most one HTTP long-poll request, so account for concurrent tabs when setting ECS task count and reverse-proxy timeouts. The route waits up to 15 seconds; configure the ingress idle timeout above that value.

The optional chat-stream relay provides exact SSE frame replay. It is not required for Postgres text durability: the web runtime continues consuming the model stream after a browser disconnect, persists batched text deltas, and reconnecting clients reload the authoritative message snapshot.

The migration shape is:

1. Consolidate repository/context boundaries before adding new persistence.
2. Make Convex optional in the browser for Postgres mode.
3. Add capability gates and route classification for every app surface.
4. Implement chat with an explicit usage policy.
5. Move business invariants into domain services.
6. Add real backend contract tests for Convex and Postgres.
7. Add Postgres-native vector search later through `pgvector`, after provider readiness and adapter work.

Capabilities:

| Capability       | Owns                                                                                                                               |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `chat`           | Chat route and chat navigation surfaces.                                                                                           |
| `files`          | File upload, file navigation, and file picker affordances.                                                                         |
| `memory`         | Memory write/read tools and memory settings.                                                                                       |
| `knowledge`      | Knowledge surfaces and knowledge search affordances.                                                                               |
| `integrations`   | Provider-neutral Composio or Executor account linking, catalogs, and external tools. Direct MCP servers remain under `mcpServers`. |
| `browserUse`     | Browser Use routes, browser task UI, and browser-session tool exposure.                                                            |
| `sandboxes`      | Daytona sandbox routes and sandbox tool exposure.                                                                                  |
| `webSearch`      | Gateway search tool exposure.                                                                                                      |
| `analytics`      | PostHog client/server initialization and capture.                                                                                  |
| `errorReporting` | Sentry initialization and capture.                                                                                                 |
| `modelRouting`   | Model provider routing and model settings surfaces.                                                                                |
| `billing`        | Checkout, portal, top-ups, billing account pages, quota purchase prompts.                                                          |
| `sso`            | OAuth/SSO buttons, enterprise auth callbacks, native auth flows.                                                                   |
| `apiKeys`        | Future API key management routes and UI.                                                                                           |
| `webhooks`       | Outbound webhook subscriptions, signed delivery, retry history, and dead letters.                                                  |
| `vectorSearch`   | Knowledge search and memory routes. Basic file listing is preserved.                                                               |
| `automations`    | Automation scheduling, automation navigation, runner callbacks.                                                                    |
| `multiTenant`    | Reserved for future tenant isolation work. The schema rejects enabling it today.                                                   |

UI hiding is not an authorization boundary. Server routes also check capabilities and return deterministic `capability_disabled` responses when disabled.

## Build and Deploy Without Vercel

Overlay is a Next.js app plus Convex. You can run the web process anywhere that can run Node and reach your Convex and object storage endpoints.

Basic Node deployment:

```bash theme={null}
npm ci
npm run build
OVERLAY_CONFIG_FILE=overlay.config.json npm run start
```

Set `NEXT_PUBLIC_APP_URL` and `NEXT_PUBLIC_CONVEX_URL` in the app runtime. If your host injects env vars at runtime, prefer env vars for secrets and keep non-secret provider shape in `overlay.config.json`.

Without Stripe:

```json theme={null}
{
  "billing": { "provider": "none", "stripe": {} },
  "capabilities": { "billing": false }
}
```

Without WorkOS:

```json theme={null}
{
  "auth": { "provider": "none", "allowDevFallbacks": false, "workos": {}, "oidc": {} },
  "capabilities": { "sso": false }
}
```

With OIDC instead of WorkOS:

```json theme={null}
{
  "auth": {
    "provider": "oidc",
    "allowDevFallbacks": false,
    "workos": {},
    "oidc": {
      "issuerUrl": "https://idp.enterprise.example.com",
      "clientId": "overlay-web",
      "clientSecret": "oidc_client_secret_placeholder",
      "audience": "overlay-api"
    }
  },
  "capabilities": { "sso": true }
}
```

## Docker Compose

Use this path when an enterprise operator wants a source checkout with a plain Compose install. The default Compose file pulls a prebuilt `ghcr.io/layernorm/overlay-web` image; local source builds are opt-in through `docker-compose.build.yml`.

This is a minimal on-prem shape for the app container. It assumes the customer already has S3-compatible object storage and does not include Convex because Convex remains a hosted deployment in the current architecture.

Quick start:

```bash theme={null}
cp .env.enterprise.example .env
cp docs/config/enterprise-private.example.json overlay.config.json
docker compose pull
docker compose up -d
```

`docker-compose.yml` mounts `overlay.config.json` into the container. The compose file intentionally does not run object storage, auth, database, browser automation, or sandbox services; those are enterprise-owned providers selected through config.

```yaml theme={null}
services:
  overlay-web:
    image: ${OVERLAY_WEB_IMAGE:-ghcr.io/layernorm/overlay-web:latest}
    ports:
      - "${OVERLAY_HTTP_PORT:-3000}:3000"
    env_file:
      - .env
    volumes:
      - ./overlay.config.json:/app/overlay.config.json:ro
    environment:
      OVERLAY_CONFIG_FILE: /app/overlay.config.json
      PORT: 3000
      HOSTNAME: 0.0.0.0
    restart: unless-stopped
```

After editing source code, build locally with the override file:

```bash theme={null}
docker compose -f docker-compose.yml -f docker-compose.build.yml up --build -d
```

Existing `docker-compose.onprem.yml` and `.env.onprem.example` files are retained as compatibility aliases for older operator instructions.

## Secrets Placement

App runtime only:

* `SESSION_SECRET`
* `SESSION_TRANSFER_KEY`
* `SESSION_COOKIE_ENCRYPTION_KEY`
* `INTERNAL_SERVICE_AUTH_SECRET`
* `API_KEY_HASH_SECRET`
* `PROVIDER_KEYS_SECRET`
* `HOOKS_TOKEN_SALT`
* WorkOS API key and OIDC client secret
* Stripe secret key, Stripe webhook secret
* R2/S3 access key secrets
* LLM provider API keys

Convex:

* `INTERNAL_API_SECRET` must be set in Convex and must match the app runtime value for that deployment.
* If you intentionally run direct Convex auth/debug paths or Convex Stripe sync actions, also configure the corresponding WorkOS or Stripe variables in that Convex deployment. The BFF-first web path should not require session cookie secrets in Convex.

Public and safe to expose:

* `NEXT_PUBLIC_APP_URL`
* `NEXT_PUBLIC_CONVEX_URL`
* `DEV_NEXT_PUBLIC_CONVEX_URL`
* `NEXT_PUBLIC_POSTHOG_TOKEN`
* `NEXT_PUBLIC_POSTHOG_HOST`
* Public CSP origins in `app.cspConnectSrc`

Values that must differ between staging and production:

* `NEXT_PUBLIC_APP_URL`
* `NEXT_PUBLIC_CONVEX_URL` and `CONVEX_DEPLOYMENT`
* `INTERNAL_API_SECRET`
* `INTERNAL_SERVICE_AUTH_SECRET`
* `SESSION_SECRET`
* `SESSION_TRANSFER_KEY`
* `SESSION_COOKIE_ENCRYPTION_KEY`
* `API_KEY_HASH_SECRET`
* WorkOS client/API credentials
* Stripe mode, keys, webhook secret, price IDs, and portal config
* R2/S3 buckets and access credentials
* LLM provider keys if you want separate billing/audit trails
* PostHog, Sentry, and analytics project identifiers

Do not copy production secrets into staging to "make it work". The config schema rejects several mixed staging/production combinations, but secret separation is still an operator responsibility.

## Convex Notes

Push Convex schema/function changes to the matching deployment:

```bash theme={null}
npm run convex:push:dev
npm run convex:push:prod
```

For local web work against the dev Convex backend, prefer:

```bash theme={null}
npm run dev:with-convex
```

Do not pass `.env.local` to production `convex deploy`; that file usually points at the dev slug. Use the package scripts so prod and dev deploys stay separate.

## Verification

Programmatic checks:

```bash theme={null}
npm run docs:health
npm run check:config
npm run typecheck
```

Minimal on-prem smoke:

```bash theme={null}
cp docs/config/onprem-minimal.example.json overlay.config.json
OVERLAY_CONFIG_FILE=overlay.config.json npm run dev
```

Verify:

* App shell loads at `http://localhost:3000/app/chat`.
* Chat page renders. With `llm.gatewayProvider="none"`, message generation is intentionally unavailable until you configure an LLM provider.
* Settings loads at `http://localhost:3000/app/settings`. In the minimal no-auth profile it should show the normal sign-in gate with SSO buttons hidden.
* Redacted system/capability state is available at `http://localhost:3000/api/v1/capabilities` in non-production environments. `/api/v1/bootstrap` also includes redacted system state after normal app authentication.
* Billing, SSO, automations, webhooks, API key management, and vector search routes return clear disabled responses when their capabilities are false.
* File upload requires a signed-in user and the configured object store. With the minimal no-auth profile, upload routes should return `Unauthorized`; with S3 enabled, create the bucket before testing authenticated uploads.

If screenshots are added to this document later, verify they do not show secrets, API keys, private hostnames, or customer data.
