# Environment reference

Everything the app reads from its environment, in one place. Configuration that describes your site lives in `docs.yaml`; the environment carries what differs per deployment: paths, connection strings, and secrets. API keys in particular are environment-only and never belong in config.

## Core

| Variable            | Default            | What it does                                                                                                             |
| ------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `READSMITH_CONTENT` | `apps/web/content` | Directory holding your docs content (where `docs.yaml` lives)                                                            |
| `DATABASE_URL`      | unset              | Postgres connection string; without it the app runs docs-only (pages and agent outputs work, search and Ask AI stay off) |
| `PORT`              | `4321`             | Host port the compose file publishes the app on                                                                          |

## Storage

| Variable                    | Default               | What it does                                                                                                     |
| --------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `STORAGE_DRIVER`            | `local`               | Bundle store driver: `local` (filesystem) or `s3` (any S3-compatible store)                                      |
| `STORAGE_ROOT`              | `apps/web/.readsmith` | `local` driver: root of the bundle store, the compiled site artifact the app serves                              |
| `STORAGE_ENDPOINT`          | unset                 | `s3` driver: scheme + host, e.g. `http://minio:9000` or your R2 account endpoint                                 |
| `STORAGE_BUCKET`            | unset                 | `s3` driver: the bucket holding the bundle store                                                                 |
| `STORAGE_ACCESS_KEY_ID`     | unset                 | `s3` driver: access key                                                                                          |
| `STORAGE_SECRET_ACCESS_KEY` | unset                 | `s3` driver: secret key                                                                                          |
| `STORAGE_REGION`            | `auto`                | `s3` driver: SigV4 region (`auto` works for R2 and MinIO)                                                        |
| `READSMITH_STORAGE_ROOT`    | unset                 | Filesystem root for persisted spec blobs (the database-backed API reference ingest); a mounted volume in compose |

The `s3` driver speaks to Cloudflare R2, MinIO, or any S3-compatible store. For
a bundled MinIO, start compose with the opt-in profile: `docker compose --profile s3 up` brings up MinIO with a console on `:9001` and creates the
bucket on first start. The default deployment needs none of this; the
filesystem driver remains the zero-config path.

Two sites must never share one storage root. Give each deployment its own `READSMITH_STORAGE_ROOT`; sharing one directory makes the second site overwrite the first site's compiled bundle.

## Git deploys

The full setup lives in [Deploy on push](/docs/self-host/git-deploys); this is the
reference. Configure exactly one auth mode: the App pair or a PAT, never both.

| Variable                     | Default      | What it does                                                                            |
| ---------------------------- | ------------ | --------------------------------------------------------------------------------------- |
| `GITHUB_APP_ID`              | unset        | App mode: the GitHub App's id (pairs with the private key)                              |
| `GITHUB_APP_PRIVATE_KEY`     | unset        | App mode: the App's private key, `\n`-escaped on one line                               |
| `GITHUB_PAT`                 | unset        | PAT mode: a fine-grained personal access token (read-only contents)                     |
| `GITHUB_WEBHOOK_SECRET`      | unset        | Verifies webhook deliveries; required for the webhook path                              |
| `GITHUB_REPO`                | unset        | The `owner/name` to serve; binding auto-builds a site with no history                   |
| `GITHUB_BRANCH`              | repo default | Build a branch other than the repository's default                                      |
| `GITHUB_POLL_INTERVAL`       | off          | Polling fallback, seconds, for instances webhooks cannot reach                          |
| `READSMITH_KEEP_DEPLOYMENTS` | `20`         | Rollback history kept per site; older snapshots prune after each publish (`0` disables) |
| `READSMITH_FAIL_ON_ERROR`    | `false`      | Strict builds: any page error fails the deploy instead of publishing with diagnostics   |

## AI keys

Keys resolve in a fixed order: a role-specific override wins, then the provider-native variable for whichever provider your `docs.yaml` names.

| Variable                       | Role                                                   |
| ------------------------------ | ------------------------------------------------------ |
| `READSMITH_AI_CHAT_KEY`        | Overrides the chat (Ask AI) key, whatever the provider |
| `READSMITH_AI_EMBEDDING_KEY`   | Overrides the embedding (semantic search) key          |
| `READSMITH_AI_RERANK_KEY`      | Overrides the rerank key, when rerank is enabled       |
| `OPENAI_API_KEY`               | Native key when a model uses the `openai` provider     |
| `ANTHROPIC_API_KEY`            | Native key for the `anthropic` provider                |
| `GOOGLE_GENERATIVE_AI_API_KEY` | Native key for the `google` provider                   |
| `AI_GATEWAY_API_KEY`           | Native key for the `gateway` provider                  |

A missing embedding key degrades search to keyword-only; a missing chat key turns Ask AI off. Neither breaks the site.

## Rate limits

Defaults protect a public deployment out of the box: Ask AI 10 requests per minute, search 60, MCP 60, per client IP. The environment overrides `docs.yaml` at deploy time.

| Variable                      | Format           | What it does                                                                   |
| ----------------------------- | ---------------- | ------------------------------------------------------------------------------ |
| `READSMITH_RATE_LIMIT`        | `true` / `false` | Master switch for the built-in limiter                                         |
| `READSMITH_RATE_LIMIT_ASK`    | `10` or `10/30`  | Ask AI policy: a count per minute, or count per N seconds                      |
| `READSMITH_RATE_LIMIT_SEARCH` | same             | Search policy                                                                  |
| `READSMITH_RATE_LIMIT_MCP`    | same             | MCP policy                                                                     |
| `READSMITH_TRUSTED_IP_HEADER` | header name      | The proxy header to read the client IP from; unset, the socket address is used |

Set `READSMITH_TRUSTED_IP_HEADER` only when a proxy you control sets that header. Trusting a client-settable header like `X-Forwarded-For` on a directly exposed app gives every request its own rate-limit bucket.

## Content-Security-Policy

CSP sources from the environment add to `docs.yaml` `security.csp` and to the strict defaults. These are read at build time, not at start: set them before `next build` (the compose image build), not on a running container.

| Variable                        | Adds sources to   |
| ------------------------------- | ----------------- |
| `READSMITH_CSP_IMG_SRC`         | `img-src`         |
| `READSMITH_CSP_CONNECT_SRC`     | `connect-src`     |
| `READSMITH_CSP_FONT_SRC`        | `font-src`        |
| `READSMITH_CSP_FRAME_SRC`       | `frame-src`       |
| `READSMITH_CSP_FRAME_ANCESTORS` | `frame-ancestors` |

## Operations

| Variable                       | Default                   | What it does                                                                        |
| ------------------------------ | ------------------------- | ----------------------------------------------------------------------------------- |
| `CACHE_DRIVER`                 | `memory`                  | The query-embedding cache driver; in-memory suits a single instance                 |
| `READSMITH_LOG_LEVEL`          | `info`                    | Log verbosity                                                                       |
| `READSMITH_WORKER_CONCURRENCY` | `2`                       | Background job concurrency                                                          |
| `READSMITH_SITE_CACHE_MAX`     | `16`                      | Hydrated site bundles held in memory; single-site installs never need more than one |
| `READSMITH_MIGRATIONS_DIR`     | resolved from the package | Where database migrations load from; the compose image sets it, you rarely will     |

## Compose-only

`POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` are consumed by the compose file to configure the database container and assemble `DATABASE_URL`. The app itself only ever reads `DATABASE_URL`.
