Files
onyx/deployment/docker_compose/env.template
T
Yuhong SunandClaude Opus 5.5 b86caa7332 feat(indexing): add new embedding models and upgrade-only model selection (#15351)
## Description

Add 11 selectable embedding models and an upgrade-only model picker.
Fresh self-hosted installs use Granite. Existing deployments keep their
stored model until an admin starts a re-index. On startup, the API
server waits until the model server can embed with the self-hosted model
in use. If that model is not in its cache, the model server downloads it
first.

| Type | Model | Output dimensions |
| --- | --- | --- |
| Self-hosted | `ibm-granite/granite-embedding-97m-multilingual-r2`
(default, in the image) | 384 |
| Self-hosted | `voyageai/voyage-4-nano` | 2048 |
| Self-hosted | `nvidia/Nemotron-3-Embed-1B-BF16` | 2048 |
| Cohere | `embed-v5.0-pro`, `embed-v5.0-fast` | 2048 |
| Google | `gemini-embedding-2` | 3072 |
| OpenAI | `text-embedding-3-large`, `text-embedding-3-small` | 3072 /
1536 |
| Voyage | `voyage-4-large`, `voyage-4`, `voyage-4-lite` (added on main)
| 1024 |

The 16 older models are now **legacy**: nomic, E5, GTE-small, Cohere
v3/v4, Voyage v2 and older Gemini/Vertex models. Existing indexes on
them keep working. The API refuses a legacy model as a new target, but a
re-index with the current model is still allowed.

## Size of this PR

After review, I removed the parts that the feature does not need. The
behavior stays the same. The diff against `main` went from **80 files,
+9,855 / −504** to **69 files, +4,740 / −467**.

| Area | Files | Added | Removed |
| --- | --- | --- | --- |
| Backend source | 16 | 1,004 | 96 |
| Backend unit tests and test managers | 16 | 1,568 | 32 |
| Backend manual suites (opt-in, not in CI) | 7 | 617 | 0 |
| Web source | 6 | 617 | 255 |
| Web tests | 5 | 630 | 1 |
| Web messages (9 locales) | 9 | 225 | 72 |
| Deployment, CI and feature map | 10 | 79 | 11 |

The feature map (`.agents/feature-map/`) now describes the new selection
rules.

Removed:

- **The branch's own cloud probe.** All selectable cloud models now use
main's probe (`probe_embedding_dimension`), which Bifrost already used.
It now also sends the stored Vertex configuration, the Azure API version
and the deployment name. Before this change, the branch's probe ignored
the Vertex configuration, so a Google provider that uses Workload
Identity could not switch to `gemini-embedding-2`.
- **The Vertex fan-out.** Main already sends `gemini-embedding-2` texts
one at a time. The Vertex code is now the same as on main.
- **The `expected_dim` path** in the model-server loader, and the
startup warning for custom models with registry names. These protected
only one rare case; see **Custom models with a registry name** in [Known
limits](#known-limits).
- **The UI fallback** that tested a new key with a default model when
the key failed with the current model. The credential test now embeds
with the staged or current model of that provider. Only a provider with
neither uses its first selectable model.
- **Picker cards for current and staged bring-your-own models.** Main
does not show them either. The current model shows in the summary at the
top of the page.
- **Shared gates and fixtures for the manual suites.** Each suite has
one gate env var.
- **The screenshots in `docs/assets`.** The images below come from an
earlier commit of this PR.
- **Tests for removed code, and repeated test cases.** The unit tests
went from 3,842 to 2,667 lines.

### CPU speed and memory cards

All nine locales show these planning estimates:

| Model | Card speed | Recommended CPU RAM |
| --- | --- | --- |
| Granite 97M | Up to **200 docs / min / CPU core** | **4 GB** per model
server |
| Voyage 4 Nano | Up to **50 docs / min / CPU core** | **4 GB** per
model server |
| Nemotron 3 Embed 1B BF16 | Up to **5 docs / min / CPU core** | **8
GB** per model server |

Nemotron says **GPU recommended**. It runs on CPU too, but slowly. The
cards tell the admin that speed changes with the hardware and the
document size. The RAM value is for one loaded model. A re-index and the
rest of Onyx need more memory.

These values are planning estimates, not guarantees. The Nemotron
per-core value is an extrapolation from an earlier multi-thread run.
Document counts depend on chunking, and throughput does not always scale
linearly with cores. The RAM values are recommendations, not measured
minimum or peak values.

## Implementation

- **Registry.** `shared_configs/embedding_models.py` defines the model
names, dimensions, prefixes, normalization, status and pinned Hugging
Face revisions. A parity test compares it with the web registry.
- **Upgrade-only guard.** `_check_embedding_model_target` runs in
`set-new-search-settings`:
- The current model can always re-index, legacy models too. The
dimension must stay the same. In Onyx Cloud, `reduced_dimension` must
also stay the same, because tenants share the index of a model.
- A selectable model must use the registry settings (dimension,
normalization, prefixes).
- Other OpenAI, Cohere, Google and Voyage models, and legacy self-hosted
models, get `INVALID_INPUT`. The error lists the selectable models.
  - Custom Hugging Face models, LiteLLM, Azure and Bifrost stay open.
- A self-hosted model gets `INVALID_INPUT` for a new
`reduced_dimension`, because the model server ignores it. A re-index
keeps a stored value.
- **Cloud probe.** Before a re-index to a selectable cloud model, the
API embeds a test text with the stored credentials and checks the
dimension. A bad key, region or model ID fails before indexing starts.
The probe runs before the PRESENT row lock, with no open transaction, as
main's Bifrost probe does.
- **Self-hosted loads.** New models load at a pinned revision with
`trust_remote_code=False`, a dimension check and a finite-vector check.
CPU-only hosts load FP32. CUDA and MPS keep the checkpoint dtype. Legacy
and custom models keep main's `SentenceTransformer` call, so their
vectors do not change.
- **Voyage 4 Nano.** An Onyx loader adds bidirectional attention and the
learned 1024-to-2048 projection. The generic loader with remote code
disabled does not apply them.
- **Model server.** All models load from the cache first, then from
Hugging Face. Loads run off the event loop on two load workers, so at
most two sets of weights (PRESENT and FUTURE) load at the same time.
Registry models get 32 tokens of headroom for the prefix. The RoPE
pre-warm now runs again only for a longer context. On main, it ran for
every change of context length.
- **Image.** The model-server image bundles pinned Granite. It no longer
bundles nomic: nomic downloads on first use. The startup cache merge
keeps the models that a volume already has.
- **Picker.** Self-hosted and cloud groups, a Legacy label on the
current legacy model, and an **Advanced** section for custom Hugging
Face models, LiteLLM, Azure and Bifrost. A staged custom model keeps the
custom provider, also when its name is the same as a registry model.
- **Error detail.** `EmbeddingModel` now keeps the model server's error
detail. Before, every HTTP failure showed "HTTP error occurred -
response is None".

### Startup wait for self-hosted models

On startup, the API server waits until the inference model server can
embed with the current self-hosted model. The model server loads the
model on the first request, and downloads it first if it is not cached.
Example: a deployment that upgrades while it uses nomic, which the new
image does not bundle.

- The API server logs the wait, each failed attempt with its cause, and
the time when the model is ready. The model server logs a download.
- `LOCAL_EMBEDDING_MODEL_STARTUP_TIMEOUT` sets the wait (default 1800
seconds). When the wait runs out, startup fails. The error names the
model server, the model, the last error and the fix. `0` disables the
wait.
- The check request runs in a daemon thread. A request that still waits
at the deadline (for example, during a slow download) therefore does not
keep the failed process alive. Before this fix, the process stayed alive
until the model server read timeout (600 s by default).
- There is no wait in Onyx Lite (`DISABLE_VECTOR_DB`), for cloud models,
in multi-tenant deployments, in integration tests, or with
`DISABLE_MODEL_SERVER`.
- The wait uses the existing embed endpoint, so it also works with an
older model server during a rolling upgrade.
- This replaces the startup call to `warm_up_bi_encoder`. `SKIP_WARM_UP`
(default `true`) skipped that call, so it did not run by default.
- Only the inference model server is checked. The indexing model server
still loads a model on first use. In Compose, the API server does not
know the host of the indexing model server.
- `env.template` and Helm `values.yaml` document the setting. The Helm
chart is now 0.9.19.

Live test at commit `0eb5881da9`, with an existing nomic deployment on a
local stack. The size reduction did not change the wait:

| Case | Result |
| --- | --- |
| nomic not cached, model server online | The model server downloaded
nomic (523 MB). Startup continued after 19 s. |
| nomic cached | Ready in 3 s, with no download. |
| Model server starts after the API server | A warning every 10 s, then
ready at 33 s. |
| nomic not cached, no internet access | Each warning showed the cause
("not in the model server's Hugging Face cache and could not be
downloaded"). Startup failed at the timeout, and the process exited. |
| Onyx Lite, no model server | No wait. |
| Timeout `0` | No wait. The model loads on first use. |

## Upgrade notes

- **Offline deployments that use nomic.** The new image does not bundle
nomic. A deployment with no internet access and no kept model cache
cannot download it. The API server then fails at startup with a clear
error, instead of starting with a search that cannot embed queries.
Before you upgrade such a deployment, add the current model to the model
server's Hugging Face cache, or keep the cache on a volume. Helm has no
cache volume by default. This PR has no schema migration, so a return to
the previous image works while the current model stays the same.
- **Helm.** The chart has no Hugging Face cache volume for the model
servers, so a model that the image does not bundle (nomic, Voyage 4
Nano, Nemotron) downloads again on every pod start. `values.yaml`
documents an opt-in volume with the existing
`inferenceCapability.volumes` and `volumeMounts` values. A default
volume is not added: a ReadWriteOnce claim hangs rolling updates across
nodes, breaks `replicaCount` above 1, and needs a StorageClass. The
bundled Granite works with or without a volume.
- **API probes.** `/health` answers only after the startup wait. A
liveness probe needs a startup probe that allows the migrations plus
`LOCAL_EMBEDDING_MODEL_STARTUP_TIMEOUT`. The api probe example in
`values.yaml` shows one.
- **Multi-tenant template snapshots.** Migration `dbaa756c2ccf` seeds a
search settings row with `DOCUMENT_ENCODER_MODEL`. When that variable is
empty, the seed is now Granite, not nomic. If a deploy runs
`alembic/check_tenant_snapshot.py`, it reports a difference in
`search_settings` until the template snapshots are built again
(`--snapshot-template`). To keep the seed the same as on main, set
`DOCUMENT_ENCODER_MODEL=nomic-ai/nomic-embed-text-v1`.
- **No return to a legacy model.** After a switch, the old index is
cleaned up and the upgrade-only rule blocks a return to a legacy model.
Keep a compatible backup before you switch.

## Audit: retries and failures

| Layer | Behavior | Compared with main |
| --- | --- | --- |
| Local encode | Catches `RuntimeError`, waits 0.1 seconds, then tries
again: **up to four encode attempts**. It also retries errors that do
not go away, such as OOM. | Unchanged. |
| API to model server | Passage requests retry request failures three
times with five-second waits. HTTP 429 allows ten attempts with
ten-second waits. Connect/read timeouts are 30/600 seconds. Query
requests do not get these retries. | Unchanged. |
| Document indexing | A failed batch falls back to one document at a
time after a two-second delay. Failures are recorded, and the other
documents continue. | Unchanged. |
| Cloud embedding | Authentication errors are separate. Errors mapped to
`RuntimeError` get exponential backoff with jitter: eight attempts in
indexing-only mode, else two. | Unchanged. `gemini-embedding-2` sends
one text per request, in sequence, as on main. |
| Model loading | Cache first, then online. A partial cache falls back
online. A failed load is not cached, so a later request can try again.
Requests for the same model and context length share one load job. |
Cache-first lookup is new for all models. Registry models add pinned
revisions and checks. |

Model downloads run off the event loop. When a waiting request is
cancelled, the shared load continues. A load job has no deadline, so it
can continue after an HTTP timeout.

One gap from main remains: the response JSON is parsed outside the retry
wrapper. See [local
retries](https://github.com/onyx-dot-app/onyx/blob/4784da70697fb59261f43088383d85671544ec98/backend/model_server/encoders.py#L157),
[request
retries](https://github.com/onyx-dot-app/onyx/blob/4784da70697fb59261f43088383d85671544ec98/backend/onyx/natural_language_processing/search_nlp_models.py#L1322),
and [cloud
retries](https://github.com/onyx-dot-app/onyx/blob/4784da70697fb59261f43088383d85671544ec98/backend/onyx/natural_language_processing/search_nlp_models.py#L942).

## Audit: migration and cancellation

There is **no schema migration, and no automatic change to existing
embedding settings**. Granite is the fresh-install default. Cloud tenant
provisioning still uses Cohere v3. Existing stored prefixes and
dimensions stay in use.

1. The API checks the target with the upgrade-only guard.
2. A new cloud target embeds a test text with the stored credentials
before the migration. The probe checks the dimension. It runs before the
PRESENT row lock.
3. The API locks PRESENT and refuses overlapping migrations. It also
refuses an index name that is live or still waiting for cleanup. FUTURE,
the first work and the cleanup consent commit together.
4. The existing port flow reads stored documents from the current index,
embeds them again and writes the new index. Connectors do not fetch
again. All-connectors mode includes paused connectors. Active-only mode
leaves them out and needs consent for the data that it leaves out. User
files are included.
5. Normal promotion waits for successful required ports, no open writers
or sync work, and a sample check in the new index. A failed or paused
required port blocks promotion. The sample check does not check the full
corpus.
6. Port batches retry up to five times. The cursor moves after a
successful batch. The first watchdog retry is immediate. Repeated
failures at the same cursor use exponential backoff from 30 seconds, up
to one hour. Five failures in sequence at that cursor pause the unit. An
operator can resume it.
7. A cancel of FUTURE keeps PRESENT active, stops port work at a batch
boundary, and removes the partial index after writers stop. **Instant
switch promotes first**, so search is not complete during backfill, and
this cancel path is not available.

See [migration
endpoint](https://github.com/onyx-dot-app/onyx/blob/4784da70697fb59261f43088383d85671544ec98/backend/onyx/server/manage/search_settings.py#L140),
[upgrade-only
guard](https://github.com/onyx-dot-app/onyx/blob/4784da70697fb59261f43088383d85671544ec98/backend/onyx/server/manage/search_settings.py#L456),
[swap
gates](https://github.com/onyx-dot-app/onyx/blob/4784da70697fb59261f43088383d85671544ec98/backend/onyx/db/swap_index.py#L367),
and
[cancellation](https://github.com/onyx-dot-app/onyx/blob/4784da70697fb59261f43088383d85671544ec98/backend/onyx/server/manage/search_settings.py#L668).

## Known limits

- **Model memory.** Two load workers limit how many models load at the
same time. Loaded models are not evicted (as on main), so the old and
new models stay in memory during a switch. Helm's default memory limit
for the indexing model server is **6 GiB**, below the **8 GB** that the
Nemotron card recommends. See [load
executor](https://github.com/onyx-dot-app/onyx/blob/4784da70697fb59261f43088383d85671544ec98/backend/model_server/encoders.py#L35)
and [Helm
limits](https://github.com/onyx-dot-app/onyx/blob/4784da70697fb59261f43088383d85671544ec98/deployment/helm/charts/onyx/values.yaml#L309).
- **Legacy models are not selectable.** This is by design. The current
legacy model stays visible and can re-index. After a switch, the
standard flow cannot select it again.
- **Bring-your-own options are under Advanced.** Unknown model names for
the built-in OpenAI, Cohere, Google and Voyage providers get
`INVALID_INPUT`. LiteLLM, Azure, Bifrost and custom models stay open.
- **The cloud probe reads the provider settings once.** If an admin
changes the key or region while a re-index starts, the probe can use the
old values. A key can also change at any time during a re-index, so no
preflight check can guarantee the key that the workers use. Indexing
failures then show in the re-index status, and the admin can cancel.
- **Custom models with a registry name.** Before this PR, an admin could
add a registry model name, such as `voyageai/voyage-4-nano`, as a custom
model. The model server now loads that name as the registry model. For
`voyage-4-nano`, the vectors then have 2048 dimensions, not 1024, so a
1024-dimension index fails with an error. Re-index to the registry model
to fix it. For the other registry models, a generic load gives the same
dimension.
- **Shared `max_seq_length`.** The model server changes `max_seq_length`
on the shared model for each request. Two requests with different
context lengths can overlap. This is the same on main. This PR does not
change it.
- **Voyage 4 Nano depends on the Transformers and SentenceTransformers
layout.** Run the real-weight suite when those dependencies change.
- **No reduced dimensions for Cohere v5 and the self-hosted models.**
Cloud provisioning stays on Cohere v3. These are follow-ups.
- **The cancel dialog has two buttons labeled Cancel.** One closes the
dialog, the other cancels the re-index. This is the same on main.
- The branch is rebased onto `main` at `ad9c0030e9`. This PR does not
claim better retrieval quality.

## Validation for this update

- **Backend: 15,343 unit tests passed** before the review fixes. I left
out the EE license tests, because they write a key to a live Redis. `ty`
and pre-commit passed. After the review fixes and the merge of `main`,
the PR's backend unit tests passed again (199 tests). Pre-commit passed
on all 69 changed files.
- **Startup exit check:** a fake model server that never answers, with a
3 s wait and a 20 s read timeout. The wait raised at 3.3 s. The process
exited at 5 s with the fix, and at 22 s without it.
- **Web: type check, lint and 3,236 Jest tests (307 suites) passed.**
- **Self-hosted real-weight suite: 6 of 6 passed.** Golden scores and
the bidirectional-attention check for Granite, Voyage 4 Nano and
Nemotron.
- **Cloud live suite: `gemini-embedding-2` passed.**
- **End to end on a local stack with branch code: 11 of 11 passed.** The
API refused all 6 legacy self-hosted targets and accepted a same-model
re-index. Then the stack switched to Granite, Voyage 4 Nano, Nemotron
and `gemini-embedding-2`, in sequence. After each switch, a pure vector
search found each of 5 seeded facts first.
- **Not run for this update: Cohere, OpenAI and Voyage cloud tests.** A
TLS proxy on the test network blocked `api.cohere.com` and
`api.openai.com`. The probe then refused the switch with `400`, as
designed. These tests passed on an earlier commit of this PR, and the
probe that they use is main's probe. I had no Voyage key.
- **CI:** at `9321cb51db`, every check passed except the Dependency
Audit: Playwright, integration, acceptance, database,
external-dependency unit, Jest and quality checks. The audit fails on
open Dependabot alerts for Pillow and certifi in other manifests
(`loadtest/uv.lock` and the Craft sandbox requirements). Other open PRs
fail the same way at the moment.
- **Not run for this update:** speed benchmarks.

<details>
<summary>Earlier validation in this PR, not run again for this
update</summary>

- 28 real-weight tests and 24 cloud tests passed.
- End-to-end switches to all eight selectable models of that time
passed. The API refused all 16 legacy targets.
- Legacy-upgrade equivalence, Docker cache keeping and multi-tenant
provisioning checks passed.
- 11,521 backend unit tests passed. 10 EE license failures also occurred
on main.

</details>

## Voyage retrieval-quality check — October 14, 2026

The branch's Voyage loader passed a local EnterpriseRAG-Bench loading
check. **Vector-only mean document Recall@10 was 91.85%**, above the
proposed 50% threshold. This supports a correct load. It does not prove
numerical equality with the upstream implementation.

The corpus had **5,000 documents**: all **722 labeled supporting
documents** and **4,278 random distractors**, sampled with seed
`20261013`. All **470 questions with document labels** were scored. The
30 questions with no labels were left out. No question or answer text
went into the corpus.

| Retrieval mode | Mean Recall@1 | Mean Recall@5 | Mean Recall@10 | Mean
Recall@20 |
| --- | --- | --- | --- | --- |
| Onyx content-vector-only retrieval | 66.13% | 87.37% | **91.85%** |
94.16% |
| Onyx hybrid search API | 75.85% | 93.32% | **96.37%** | 97.70% |

Recall is the fraction of labeled documents found for each question,
averaged over the questions. Rankings use the first hit of each
document. Vector-only hit rate@10 (at least one relevant document) was
94.26%. That is a different metric from recall.

| Question category | Questions | Vector-only Recall@10 | Hybrid
Recall@10 |
| --- | --- | --- | --- |
| Basic | 175 | 97.71% | 99.43% |
| Semantic | 125 | 84.80% | 92.80% |
| Intra-document reasoning | 40 | 97.50% | 100.00% |
| Project related | 40 | 86.43% | 95.35% |
| Constrained | 30 | 98.33% | 98.33% |
| Conflicting information | 20 | 90.00% | 97.50% |
| Completeness | 20 | 68.03% | 79.03% |
| Miscellaneous | 20 | 100.00% | 100.00% |

The check used CPU FP32, 2048-dimensional normalized vectors, and the
registry's query and document prefixes. MPS and CUDA were off. There was
no query expansion, reranking, or LLM document selection. All 5,000
document IDs matched the saved manifest, with 15,612 indexed chunks. All
query vectors were finite and had unit length. The last indexing attempt
had no document errors. Two earlier attempts stopped after
stale-heartbeat timeouts and continued from saved progress.

**Search path:** on this OpenSearch branch, `hybrid_alpha=1` still runs
hybrid retrieval, so the API result includes keyword scores. The
vector-only result calls `OpenSearchDocumentIndex.semantic_retrieval`
directly, with content vectors only. Keyword matches therefore cannot
hide a broken embedding loader in that result.

**Scope:** this is an easier 5,000-document subset that contains all
gold documents, not the full 511,958-document benchmark. Do not compare
these values with full-corpus leaderboard scores. No loader or ranking
code changed to get these results.

Revisions: Onyx `913bfa8d7a32c9cda340df92f42d0f706fd424c2`;
EnterpriseRAG-Bench `d36685e273713975ee20299bbf1ab64165575b3c`; Voyage
`67fabc9bef010dabc5f6024aa1b1b6b93410426f`. Runtime: PyTorch 2.9.1,
Transformers 5.14.1, SentenceTransformers 5.4.1.

## UI screenshots

From the local development stack at commit `98bfc70080`, before the size
reduction. These show the real flow, not mockups.

<details open>
<summary>Self-hosted cards and current model</summary>

![Self-hosted model cards with CPU throughput and RAM
guidance](https://raw.githubusercontent.com/onyx-dot-app/onyx/98bfc70080a541f9346517d062f1c9edbdd51ffb/docs/assets/embedding-models/01-model-cards.jpg)

</details>

<details>
<summary>Cloud model catalog</summary>

![Cloud model
catalog](https://raw.githubusercontent.com/onyx-dot-app/onyx/98bfc70080a541f9346517d062f1c9edbdd51ffb/docs/assets/embedding-models/02-cloud-models.jpg)

</details>

<details>
<summary>Staged Granite change and migration strategy</summary>

![Staged model
change](https://raw.githubusercontent.com/onyx-dot-app/onyx/98bfc70080a541f9346517d062f1c9edbdd51ffb/docs/assets/embedding-models/03-staged-change.jpg)

</details>

<details>
<summary>Re-index in progress</summary>

![Re-index progress and cancellation
action](https://raw.githubusercontent.com/onyx-dot-app/onyx/98bfc70080a541f9346517d062f1c9edbdd51ffb/docs/assets/embedding-models/04-reindex-progress.jpg)

</details>

<details>
<summary>Cancel confirmation</summary>

![Cancel re-index
confirmation](https://raw.githubusercontent.com/onyx-dot-app/onyx/98bfc70080a541f9346517d062f1c9edbdd51ffb/docs/assets/embedding-models/05-cancel-confirmation.jpg)

</details>

<details>
<summary>Advanced custom model setup</summary>

![Custom embedding model
setup](https://raw.githubusercontent.com/onyx-dot-app/onyx/98bfc70080a541f9346517d062f1c9edbdd51ffb/docs/assets/embedding-models/06-custom-model-dialog.jpg)

</details>

## Additional Options

- [ ] [Optional] Please cherry-pick this PR to the latest release
version.
- [ ] [Optional] Override Linear Check

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:40:21 +00:00

502 lines
22 KiB
Plaintext

# Copy this file to .env so it's picked up by the docker compose yaml files
# Uncomment the values you would like to set
# No edits necessary, works out of the box
################################################################################
## COMMONLY MODIFIED CONFIGURATIONS
################################################################################
## Version of Onyx to deploy, default is latest (main built nightly).
## Craft uses this same tag for the sandbox image; do not set a separate
## sandbox image for normal deployments.
## Every image publishes a -dev twin for each of its tags (e.g. latest-dev,
## v1.2.3-dev), so IMAGE_TAG=latest-dev selects the dev variant of the whole
## deployment. Today only the backend image actually differs: its -dev twin adds
## debugging tools (vim, nano, curl, ps, psql) that the default image leaves out.
IMAGE_TAG=latest
## The code-interpreter (Python sandbox) service ships on its own release line,
## so IMAGE_TAG above does not cover it. The compose files pin a tested version;
## set this only to run a different one.
# CODE_INTERPRETER_IMAGE_TAG=
## Onyx Craft Configuration (off by default).
## Craft requires a Business or Enterprise license. The Community Edition shows an
## upgrade page instead, until Craft merges into the main chat.
## When enabling Craft through the installer's --include-craft or by manually adding
## docker-compose.craft.yml, the sandbox image follows IMAGE_TAG above.
# ENABLE_CRAFT=true
#
## docker backend drives sandboxes via the host Docker socket (root-equivalent;
## trusted hosts only) and needs v4.0.6+ images. The installer picks docker vs
## kubernetes by image tag.
# SANDBOX_BACKEND=docker
#
## ONYX_SERVER_URL is the complete API base URL used by Onyx clients. The Craft
## compose overlay defaults it to its private onyx-craft-api network alias,
## which works on Docker Desktop and Linux without exposing another host port.
## Override only when routing sandbox API traffic through a public reverse
## proxy, including the /api path prefix served by that proxy.
# ONYX_SERVER_URL=https://onyx.your-org.example/api
#
## Sandbox bridge network. Created by the installer (or manually:
## docker network create onyx_craft_sandbox
## Sandboxes join only this network. The firewall forces API and public egress
## through sandbox-proxy. Postgres, redis, and the object store stay off the bridge.
# SANDBOX_DOCKER_NETWORK=onyx_craft_sandbox
#
## Optional IPv6-only Docker sandbox bridge. Before the first Craft install:
## docker network create --ipv4=false --ipv6 --subnet fd42:6f6e:7978::/64 onyx_craft_sandbox
## The sandbox listener follows the bridge. Set the proxy listener to ::.
## Route the API through the proxy on the original Compose network.
# ONYX_SERVER_URL=http://api_server:8080
## Start with the bridge subnet below. Append all other internal VPC, host,
## and Docker CIDRs, including globally routable IPv6 ranges.
# SANDBOX_PROXY_LISTEN_HOST=::
# SANDBOX_PROXY_INTERNAL_CIDRS=fd42:6f6e:7978::/64
## Keep false for ULA bridges. Global IPv6 clients require restricted proxy ingress.
# SANDBOX_PROXY_ALLOW_GLOBAL_CLIENTS=false
#
## Per-sandbox container resource limits. Defaults match K8s pod *requests*
## (1 CPU / 2Gi), not its limits (2 CPU / 10Gi), since single-VM compose
## deployments rarely have headroom to over-commit every sandbox.
# SANDBOX_DOCKER_MEMORY_LIMIT=2g
# SANDBOX_DOCKER_CPU_LIMIT=1.0
## EC2 IMDS note: on EC2 the Docker daemon's default bridge allows traffic to
## 169.254.169.254 (Instance Metadata Service), which can hand out IAM role
## credentials. The installer does not block this automatically: require
## IMDSv2 (HttpTokens=required) on the instance, and/or add a host-level
## DOCKER-USER iptables rule that drops bridge traffic to that address.
## Air-gapped deployments: set to "true" to disable outbound calls to
## onyx-controlled endpoints (recommended-models sync, remote model catalog
## lookup, release notes). Inbound traffic and user-configured integrations
## are unaffected.
# ONYX_AIRGAPPED=
## Auth Settings
### https://docs.onyx.app/deployment/authentication
### Authentication is always enabled. SSO (Google / OIDC / SAML) is configured
### in the admin panel (Admin Panel > Organization > SSO Providers).
# SESSION_EXPIRE_TIME_SECONDS=
### Signs password reset, email verification, OAuth login state, and captcha cookies.
### REQUIRED: the API server refuses to start with an empty value (secure by default).
### The installer auto-generates this; if setting manually, run: openssl rand -hex 32
USER_AUTH_SECRET=""
### Recommend to set this for security
# ENCRYPTION_KEY_SECRET=
### Bearer token for the API server's /metrics endpoint. Auth is REQUIRED by
### default: scrapers must send `Authorization: Bearer <token>` (Prometheus
### scrape config: `authorization`). If you set neither this token nor
### DISABLE_METRICS_AUTH, /metrics is locked (returns 401).
# METRICS_AUTH_TOKEN=
### Set to "true" to expose /metrics with NO authentication (explicit opt-out).
# DISABLE_METRICS_AUTH=
### Set to "true" to expose the interactive API docs and schema (/openapi.json,
### /docs, /redoc) publicly with NO authentication. Off by default so the API
### surface is not exposed; when off these routes are not registered (404).
# ENABLE_PUBLIC_DOCS=
### Optional
# API_KEY_HASH_ROUNDS=
### You can add a comma separated list of domains like onyx.app, only those domains will be allowed to signup/log in
# VALID_EMAIL_DOMAINS=
### Set to "true" to restrict GET /users to admins. Non-admins can't enumerate
### accounts or share agents with individual users; group sharing still works.
# USER_DIRECTORY_ADMIN_ONLY=
## Search Configuration
## TEMPORARY (self-hosted only, will be removed soon): comma-separated document set
## NAMES. When set, the Onyx Search UI returns results only from those sets (chat is
## unaffected). A name that doesn't exist matches nothing and is logged. Empty = off.
# FORCED_DOCUMENT_SET_NAMES=
## Chat Configuration
# HARD_DELETE_CHATS=
# MAX_ALLOWED_UPLOAD_SIZE_MB=250
# Default per-user upload size limit (MB) when no admin value is set.
# Automatically clamped to MAX_ALLOWED_UPLOAD_SIZE_MB at runtime.
# DEFAULT_USER_FILE_MAX_UPLOAD_SIZE_MB=100
## open_url (web page fetch) limits
## Decoded body caps in bytes. The read stops at the larger of the two.
# OPEN_URL_MAX_HTML_SIZE_BYTES=20971520
# OPEN_URL_MAX_PDF_SIZE_BYTES=52428800
## Wall-clock limit in seconds for reading one response body.
# OPEN_URL_BODY_DEADLINE_SECONDS=120
## Max URLs in one /web-search/open-urls request or MCP open_urls call.
# OPEN_URLS_MAX_URLS_PER_REQUEST=20
## Base URL for redirects
# WEB_DOMAIN=
## Set to false to let any origin embed Onyx in an <iframe>, disabling
## clickjacking protection. Protected by default.
# WEB_FRAME_PROTECTION_ENABLED=false
## Set to true to add strict Content-Security-Policy directives
## (default-src/script-src/connect-src/etc.) restricting where scripts load
## from and where data can be sent. Off by default.
# WEB_STRICT_CSP_ENABLED=true
################################################################################
## SERVICES CONFIGURATIONS
################################################################################
## Database Configuration
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
# POSTGRES_DB=
# POSTGRES_DEFAULT_SCHEMA=
# POSTGRES_USE_NULL_POOL=
# POSTGRES_API_SERVER_POOL_SIZE=
# POSTGRES_API_SERVER_POOL_OVERFLOW
# POSTGRES_POOL_RECYCLE=
# Client-side TCP keepalives for the sync engine (sent on idle sockets to
# stop PgBouncer / NLB from silently closing them). Enabled by default.
# POSTGRES_TCP_KEEPALIVES=
# POSTGRES_TCP_KEEPALIVES_IDLE=
# POSTGRES_TCP_KEEPALIVES_INTERVAL=
# POSTGRES_TCP_KEEPALIVES_COUNT=
# --- Database TLS (libpq sslmode vocabulary; ignored when USE_IAM_AUTH=true) ---
# Pick the level you need; each builds on the one above. Default (unset) = no TLS.
#
# require encrypt only — does NOT detect a MITM (server is not authenticated)
# verify-ca + verify the server cert chains to POSTGRES_SSLROOTCERT
# verify-full + also verify the server hostname <- recommended for managed DBs
#
# verify-ca / verify-full REQUIRE POSTGRES_SSLROOTCERT (CA bundle path; use the
# system bundle for publicly-trusted server certs).
# POSTGRES_SSLMODE= # disable | allow | prefer | require | verify-ca | verify-full
# POSTGRES_SSLROOTCERT= # e.g. /etc/ssl/certs/rds-ca-bundle.pem
#
# Mutual TLS (optional, rarely needed): also present a CLIENT cert so the server
# can authenticate us. Most deployments do NOT set these. Requires a POSTGRES_SSLMODE
# that negotiates SSL (require / verify-ca / verify-full); cert and key must both be set.
# POSTGRES_SSLCERT= # client certificate path
# POSTGRES_SSLKEY= # client private key path
# POSTGRES_SSLKEY_PASSWORD= # optional, only if the key file is encrypted
# DB_READONLY_USER=
# DB_READONLY_PASSWORD=
## File Store Backend: "s3" (default, uses the bundled object store) or "postgres" (no extra services needed)
## COMPOSE_PROFILES activates the object store and the legacy MinIO services. To use PostgreSQL file storage instead,
## remove s3-filestore from COMPOSE_PROFILES and set FILE_STORE_BACKEND=postgres.
COMPOSE_PROFILES=s3-filestore
FILE_STORE_BACKEND=s3
## Object store configuration (only needed when FILE_STORE_BACKEND=s3)
## The bundled object store is SeaweedFS. It uses the S3_AWS_* pair as its admin credentials.
## SECURITY: minioadmin is a local-dev default. The installer randomizes it. If you
## edit .env by hand, change all four values before production. Keep MINIO_ROOT_*
## equal to the S3_AWS_* pair, since earlier releases start MinIO from them.
S3_ENDPOINT_URL=http://object-store:8333
S3_AWS_ACCESS_KEY_ID=minioadmin
S3_AWS_SECRET_ACCESS_KEY=minioadmin
S3_FILE_STORE_BUCKET_NAME=onyx-file-store-bucket
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
## Earlier releases kept files in the minio service, which still runs. The installer sets
## S3_LEGACY_ENDPOINT_URL so writes reach both stores and misses fall back to MinIO, which
## lets a rollback to an earlier release find every file. Uncomment it on a bundled install
## you set up by hand. Leave it commented for external storage, or files also land in MinIO.
# S3_LEGACY_ENDPOINT_URL=http://minio:9000
## Keys for the legacy store when they differ from the S3_AWS_* pair.
# S3_LEGACY_AWS_ACCESS_KEY_ID=
# S3_LEGACY_AWS_SECRET_ACCESS_KEY=
## object-store-copy finishes once a pass copies nothing and this many seconds have passed since it started.
# LEGACY_COPY_SETTLE_SECONDS=600
# LEGACY_COPY_WORKERS=16
## Retire MinIO once object-store-copy logs that the copy is complete. The app keeps running:
## docker compose exec object-store-copy python -m onyx.file_store.legacy_copy --retire
## Once it reports MinIO retired, set MINIO_REPLICAS=0 and run `docker compose up -d minio`.
## Its minio_data volume stays until you remove it. A rollback to a release before the
## object store then misses files written after retiring.
## The installer sets 0 on a fresh install, which never held files in MinIO.
# MINIO_REPLICAS=1
## GCS Configuration (only needed when FILE_STORE_BACKEND=gcs)
## No sidecar container needed — GCS is a managed service.
## Remove s3-filestore from COMPOSE_PROFILES when using FILE_STORE_BACKEND=gcs.
## By default, Application Default Credentials (ADC) are used, which supports
## GKE Workload Identity, Compute Engine instance SA, and gcloud CLI auth.
# FILE_STORE_BACKEND=gcs
# GCS_FILE_STORE_BUCKET_NAME=your-gcs-bucket-name
# GCS_FILE_STORE_PREFIX=onyx-files
# GCS_PROJECT_ID=your-gcp-project
# GCS_SERVICE_ACCOUNT_KEY_PATH=/path/to/service-account-key.json
# GCS_SERVICE_ACCOUNT_KEY_JSON=
## Azure Blob Storage Configuration (only needed when FILE_STORE_BACKEND=azure)
## No sidecar container needed — Azure Blob Storage is a managed service.
## Remove s3-filestore from COMPOSE_PROFILES when using FILE_STORE_BACKEND=azure.
## Auth priority: connection string, then account key, then DefaultAzureCredential
## (AKS Workload Identity, managed identity, or az CLI auth).
# FILE_STORE_BACKEND=azure
# AZURE_FILE_STORE_CONTAINER_NAME=your-container-name
# AZURE_FILE_STORE_PREFIX=onyx-files
# AZURE_STORAGE_ACCOUNT_NAME=yourstorageaccount
## Endpoint override — derived from the account name when unset. Set explicitly
## for Azurite or sovereign clouds (e.g. *.blob.core.usgovcloudapi.net).
# AZURE_STORAGE_ACCOUNT_URL=
# AZURE_STORAGE_CONNECTION_STRING=
# AZURE_STORAGE_ACCOUNT_KEY=
## Nginx Proxy Timeout Configuration (in seconds)
## These settings control how long nginx waits for upstream servers (api_server/web_server)
## Increase these values if you experience timeout errors with long-running requests
# NGINX_PROXY_CONNECT_TIMEOUT=300
# NGINX_PROXY_SEND_TIMEOUT=300
# NGINX_PROXY_READ_TIMEOUT=300
## MCP Server Configuration
## The MCP (Model Context Protocol) server allows external MCP clients to interact with Onyx
## Set to false to disable the MCP server (enabled by default)
# MCP_SERVER_ENABLED=true
## Port for the MCP server (defaults to 8090)
# MCP_SERVER_PORT=8090
## CORS origins for MCP clients (comma-separated list)
# MCP_SERVER_CORS_ORIGINS=
## Read timeout in seconds for MCP server requests to the API server (defaults to 300)
# MCP_SERVER_API_REQUEST_TIMEOUT_SECONDS=300
## Discord Bot Configuration
## The Discord bot allows users to interact with Onyx from Discord servers
## Bot token from Discord Developer Portal (required to enable the bot)
# DISCORD_BOT_TOKEN=
## Command prefix for bot commands (default: "!")
# DISCORD_BOT_INVOKE_CHAR=!
## Celery Configuration
# CELERY_BROKER_POOL_LIMIT=
# CELERY_WORKER_DOCFETCHING_CONCURRENCY=
# CELERY_WORKER_DOCPROCESSING_CONCURRENCY=
# CELERY_WORKER_LIGHT_CONCURRENCY=
# CELERY_WORKER_LIGHT_PREFETCH_MULTIPLIER=
## Time limit in seconds for one doc permission sync or group sync (default:
## JOB_TIMEOUT_SECONDS, 6h). Raise it for a source whose full sync takes longer.
# PERMISSION_SYNC_TIMEOUT_SECONDS=
## Document-index sync beat lock TTL in seconds (default 120). Raise it if the
## sync pass aborts with "no longer owned" or "Lock not owned on completion".
# CELERY_DOCUMENT_SYNC_BEAT_LOCK_TIMEOUT=
## AWS Configuration
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# AWS_REGION_NAME=
# Set to true when using IAM authentication for Postgres connections.
USE_IAM_AUTH=false
################################################################################
## DEVELOPER, DEBUGGING, AND LOGGING
################################################################################
## Logging and Telemetry
# LOG_LEVEL is the global knob for log verbosity across every Onyx process:
# api_server, web_server, model servers, slack/discord bots, AND all Celery
# workers (primary, light, heavy, docprocessing, docfetching, monitoring,
# scheduled_tasks, user_file_processing, beat). For per-worker overrides,
# explicitly add `--loglevel=<value>` to that worker's command in
# backend/supervisord.conf — an explicit CLI flag takes precedence over
# LOG_LEVEL inside the worker process.
LOG_LEVEL=info
LOG_ONYX_MODEL_INTERACTIONS=False
# LOG_ENDPOINT_LATENCY=
# LOG_POSTGRES_LATENCY=
# LOG_POSTGRES_CONN_COUNTS=
# DISABLE_TELEMETRY=
## Feature Flags
# SHOW_EXTRA_CONNECTORS=true
# DISABLE_MODEL_SERVER=false
## Analytics
# SENTRY_DSN=
## Demo/Testing
# MOCK_CONNECTOR_FILE_PATH=
################################################################################
## ADVANCED CONFIGURATIONS
################################################################################
## SlackBot Configuration
# ONYX_BOT_DISABLE_DOCS_ONLY_ANSWER=
# ONYX_BOT_FEEDBACK_VISIBILITY=
# ONYX_BOT_DISPLAY_ERROR_MSGS=
# NOTIFY_SLACKBOT_NO_ANSWER=
# ONYX_BOT_MAX_QPM=
# ONYX_BOT_MAX_WAIT_TIME=
## Advanced Auth Settings
# REQUIRE_EMAIL_VERIFICATION=
# SMTP_SERVER=
# SMTP_PORT=
# SMTP_USER= # Optional - omit for unauthenticated relays
# SMTP_PASS= # Optional - omit for unauthenticated relays
# SMTP_STARTTLS=true # Set to "false" for servers that don't support TLS
# ENABLE_EMAIL_INVITES=
# EMAIL_FROM= # Required when SMTP_USER is not set
# EMAIL_ARCHIVE_BCC_ADDRESSES= # Optional comma-separated BCC archive recipients for all emails
# TRACK_EXTERNAL_IDP_EXPIRY=
# Opt-in: capture IdP directory claims at OAuth/OIDC login to enrich chat
# prompts and resolve {{user.<key>}} agent placeholders. Sends directory data
# to the configured LLM, so off by default.
# IDP_PROFILE_ENRICHMENT_ENABLED=
# Optional JSON overriding directory claim aliases per field, e.g.
# '{"department": ["dept", "division"], "country": ["c"]}'.
# IDP_PROFILE_CLAIM_MAP=
### Legacy pre-v4.4 SSO vars. Imported into an SSO provider entry on upgrade.
### Planned for removal in v4.5.
# AUTH_TYPE=
# GOOGLE_OAUTH_CLIENT_ID=
# GOOGLE_OAUTH_CLIENT_SECRET=
# OAUTH_CLIENT_ID=
# OAUTH_CLIENT_SECRET=
# OPENID_CONFIG_URL=
# SAML_CONF_DIR=
# Comma-separated list of origins allowed to make cross-origin (CORS) requests,
# e.g. "https://onyx.example.com". When unset, all origins are allowed but
# credentialed (cookie-authenticated) cross-origin requests are disabled.
# CORS_ALLOWED_ORIGIN=
# INTEGRATION_TESTS_MODE=
# JWT_PUBLIC_KEY_URL=
# JWT_EXPECTED_AUDIENCE=
# JWT_EXPECTED_ISSUER=
## Gen AI Settings
# GEN_AI_MAX_TOKENS=
# LLM_SOCKET_READ_TIMEOUT=
# IMAGE_SUMMARIZATION_TIMEOUT=
# CONTEXTUAL_RAG_LLM_TIMEOUT=
# MAX_CHUNKS_FED_TO_CHAT=
# DISABLE_LITELLM_STREAMING=
# LITELLM_EXTRA_HEADERS=
# GEN_AI_API_KEY=
# GENERATIVE_MODEL_ACCESS_CHECK_FREQ=
# LITELLM_CUSTOM_ERROR_MESSAGE_MAPPINGS=
## Usage / cost accounting
# Records priced generation spans into the per-user usage ledger (default on).
# Set to "false" to disable the recording processor entirely.
# USER_USAGE_TRACKING_ENABLED=true
# Flat per-image cost in cents when litellm has no price for an image model (default 4).
# DEFAULT_IMAGE_COST_CENTS=4
# Fallback USD per million tokens when litellm can't price a model (default 0 = free).
# DEFAULT_LLM_INPUT_COST_PER_MTOK=0
# DEFAULT_LLM_OUTPUT_COST_PER_MTOK=0
## Query Options
# DOC_TIME_DECAY=
# HYBRID_ALPHA=
# USE_SEMANTIC_KEYWORD_EXPANSIONS_BASIC_SEARCH=
## Model Configuration
# EMBEDDING_BATCH_SIZE=
# DOCUMENT_ENCODER_MODEL=
# DOC_EMBEDDING_DIM=
# NORMALIZE_EMBEDDINGS=
# ASYM_QUERY_PREFIX=
# ASYM_PASSAGE_PREFIX=
# DISABLE_RERANK_FOR_STREAMING=
# MODEL_SERVER_PORT=
# Seconds the API server waits on startup for the model server to load a self-hosted
# embedding model, including a download if it is not cached (default 1800). Startup
# fails when the wait runs out. 0 disables the wait.
# LOCAL_EMBEDDING_MODEL_STARTUP_TIMEOUT=
# INDEX_BATCH_SIZE=
# MIN_THREADS_ML_MODELS=
## Indexing Configuration
# ENABLED_CONNECTOR_TYPES=
# DISABLE_INDEX_UPDATE_ON_SWAP=
# Index spreadsheets and other tabular files (xlsx, Google Sheets, CSV, TSV) by title only,
# with "<content-skipped>" as their content (default false)
# SKIP_SPREADSHEET_CONTENT=
# Re-chunk tabular documents indexed by an older chunking strategy, in place and
# without re-crawling the source (default false). Only arms the migration: a
# stored record stops it once done, so it is safe to leave set.
# TABULAR_RECHUNK_ENABLED=
# OpenSearch Point-In-Time keep-alive for the reindex/port scan (default 5m)
# PIT_KEEP_ALIVE=5m
# CONTINUE_ON_CONNECTOR_FAILURE=
# CONFLUENCE_CONNECTOR_LABELS_TO_SKIP=
# JIRA_CONNECTOR_LABELS_TO_SKIP=
# JIRA_SERVER_API_VERSION=
# JIRA_CLOUD_API_VERSION=
# CANVAS_PERMISSION_DOC_SYNC_FREQUENCY=
# CANVAS_PERMISSION_GROUP_SYNC_FREQUENCY=
# AIRTABLE_ATTACHMENT_SIZE_THRESHOLD=10485760
# DROPBOX_CONNECTOR_SIZE_THRESHOLD=20971520
# ONEDRIVE_PERMISSION_DOC_SYNC_FREQUENCY=
# ONEDRIVE_PERMISSION_GROUP_SYNC_FREQUENCY=
# BOX_CONNECTOR_SIZE_THRESHOLD=
# SHAREPOINT_CONNECTOR_SIZE_THRESHOLD=
# OUTLOOK_CONNECTOR_ATTACHMENT_SIZE_THRESHOLD=
# TEAMS_CONNECTOR_ATTACHMENT_SIZE_THRESHOLD=
# TEAMS_PERMISSION_GROUP_SYNC_FREQUENCY=
# OUTLOOK_PERMISSION_DOC_SYNC_FREQUENCY=
# BOX_PERMISSION_DOC_SYNC_FREQUENCY=
# BOX_PERMISSION_GROUP_SYNC_FREQUENCY=
# GONG_CONNECTOR_START_TIME=
# ZOOM_TRANSCRIPT_LAG_BUFFER_HOURS=
# ZOOM_PERMISSION_GROUP_SYNC_FREQUENCY=
# ZOOM_PERMISSION_DOC_SYNC_FREQUENCY=
# LINEAR_PERMISSION_GROUP_SYNC_FREQUENCY=
# LINEAR_PERMISSION_DOC_SYNC_FREQUENCY=
# YOUTRACK_PERMISSION_GROUP_SYNC_FREQUENCY=
# YOUTRACK_PERMISSION_DOC_SYNC_FREQUENCY=
# HUBSPOT_PERMISSION_DOC_SYNC_FREQUENCY=
# NOTION_CONNECTOR_DISABLE_RECURSIVE_PAGE_LOOKUP=
# GITHUB_CONNECTOR_BASE_URL=
# MAX_DOCUMENT_CHARS=
# MAX_FILE_SIZE_BYTES=
# Push every successfully indexed public-connector document to an external HTTP
# endpoint (failures are logged and never fail indexing). Intended for non-EE
# deployments — EE users should configure the Document Push hook in the admin
# UI instead. Not supported in multi-tenant deployments.
# DOCUMENT_PUSH_ENDPOINT_URL=
# DOCUMENT_PUSH_API_KEY=
# DOCUMENT_PUSH_TIMEOUT_SECONDS=30
## OAuth Connector Configs
# EGNYTE_CLIENT_ID=
# EGNYTE_CLIENT_SECRET=
# LINEAR_CLIENT_ID=
# LINEAR_CLIENT_SECRET=
# SALESFORCE_CLIENT_ID=
# SALESFORCE_CLIENT_SECRET=
## Miscellaneous
# Hides "Powered by Onyx" on Enterprise. Do not set without explicit permission from Onyx.
# HIDE_ONYX_BRANDING=
# Kill switch for the gateway's native Anthropic passthrough (server tools,
# thinking-block signatures, fine-grained streaming); default on. Set to false
# to send Anthropic-backed providers back through the OpenAI-translation path.
# ANTHROPIC_GATEWAY_PASSTHROUGH_ENABLED=false
# Kill switch for the gateway's native OpenAI Responses passthrough (hosted
# tools, encrypted reasoning, fine-grained streaming); default on. Set to
# false to send true-OpenAI models back through the translation path.
# OPENAI_GATEWAY_PASSTHROUGH_ENABLED=false
# ONYX_QUERY_HISTORY_TYPE=
# Hide the Query History page from the admin sidebar (visibility-only; the history
# APIs + recording keep working, unlike ONYX_QUERY_HISTORY_TYPE=disabled).
# HIDE_QUERY_HISTORY_FROM_ADMIN_PANEL=true
# CHECK_TTL_MANAGEMENT_TASK_FREQUENCY_IN_HOURS=
## Frontend Configs
# NEXT_PUBLIC_DISABLE_LOGOUT=
# NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED=
# NEXT_PUBLIC_THEME=
# NEXT_PUBLIC_CUSTOM_REFRESH_URL=
## Pointer to services
POSTGRES_HOST=relational_db
REDIS_HOST=cache
# Redis socket deadlines in seconds for the app client (not Celery). A peer that
# stops replying but keeps the TCP session open raises after this instead of
# holding a thread until the container restarts. Raise them for a slow Redis.
# REDIS_SOCKET_TIMEOUT=30
# REDIS_SOCKET_CONNECT_TIMEOUT=10
MODEL_SERVER_HOST=inference_model_server
INDEXING_MODEL_SERVER_HOST=indexing_model_server
INTERNAL_URL=http://api_server:8080