## File: README.md

<a name="readme-top"></a>

<h2 align="center">
    [ ](https://www.onyx.app/?utm_source=onyx_repo&utm_medium=github&utm_campaign=readme)
</h2>





# Onyx - The Open Source AI Platform

**[Onyx](https://www.onyx.app/?utm_source=onyx_repo&utm_medium=github&utm_campaign=readme)** is the application layer for LLMs - bringing a feature-rich interface that can be easily hosted by anyone.
Onyx enables LLMs through advanced capabilities like RAG, web search, code execution, file creation, deep research and more.

Connect your applications with over 50+ indexing based connectors provided out of the box or via MCP.

> [!TIP]
> Deploy with a single command:
> ```
> curl -fsSL https://onyx.app/install_onyx.sh | bash
> ```



---

## ⭐ Features

- **🔍 Agentic RAG:** Get best in class search and answer quality based on hybrid index + AI Agents for information retrieval
  - Benchmark to release soon!
- **🔬 Deep Research:** Get in depth reports with a multi-step research flow.
  - Top of [leaderboard](https://github.com/onyx-dot-app/onyx_deep_research_bench) as of Feb 2026.
- **🤖 Custom Agents:** Build AI Agents with unique instructions, knowledge, and actions.
- **🌍 Web Search:** Browse the web to get up to date information.
  - Supports Serper, Google PSE, Brave, SearXNG, and others.
  - Comes with an in house web crawler and support for Firecrawl/Exa.
- **📄 Artifacts:** Generate documents, graphics, and other downloadable artifacts.
- **▶️ Actions & MCP:** Let Onyx agents interact with external applications, comes with flexible Auth options.
- **💻 Code Execution:** Execute code in a sandbox to analyze data, render graphs, or modify files.
- **🎙️ Voice Mode:** Chat with Onyx via text-to-speech and speech-to-text.
- **🎨 Image Generation:** Generate images based on user prompts.

Onyx supports all major LLM providers, both self-hosted (like Ollama, LiteLLM, vLLM, etc.) and proprietary (like Anthropic, OpenAI, Gemini, etc.).

To learn more - check out our [docs](https://docs.onyx.app/welcome?utm_source=onyx_repo&utm_medium=github&utm_campaign=readme)!

---

## 🚀 Deployment Modes

> Onyx supports deployments in Docker, Kubernetes, Helm/Terraform and provides guides for major cloud providers.
> Detailed deployment guides found [here](https://docs.onyx.app/deployment/overview).

Onyx supports two separate deployment options: standard and lite.

#### Onyx Lite

The Lite mode can be thought of as a lightweight Chat UI. It requires less resources (under 1GB memory) and runs a less complex stack.
It is great for users who want to test out Onyx quickly or for teams who are only interested in the Chat UI and Agents functionalities.

#### Standard Onyx

The complete feature set of Onyx which is recommended for serious users and larger teams. Additional components not included in Lite mode:
- Vector + Keyword index for RAG.
- Background containers to run job queues and workers for syncing knowledge from connectors.
- AI model inference servers to run deep learning models used during indexing and inference.
- Performance optimizations for large scale use via in memory cache (Redis) and blob store (MinIO).

> [!TIP]  
> **To try Onyx for free without deploying, visit [Onyx Cloud](https://cloud.onyx.app/signup?utm_source=onyx_repo&utm_medium=github&utm_campaign=readme)**.

---

## 🏢 Onyx for Enterprise

Onyx is built for teams of all sizes, from individual users to the largest global enterprises:
- 👥 Collaboration: Share chats and agents with other members of your organization.
- 🔐 Single Sign On: SSO via Google OAuth, OIDC, or SAML. Group syncing and user provisioning via SCIM.
- 🛡️ Role Based Access Control: RBAC for sensitive resources like access to agents, actions, etc.
- 📊 Analytics: Usage graphs broken down by teams, LLMs, or agents.
- 🕵️ Query History: Audit usage to ensure safe adoption of AI in your organization.
- 💻 Custom code: Run custom code to remove PII, reject sensitive queries, or to run custom analysis.
- 🎨 Whitelabeling: Customize the look and feel of Onyx with custom naming, icons, banners, and more.

## 📚 Licensing

There are two editions of Onyx:

- Onyx Community Edition (CE) is available freely under the MIT license and covers all of the core features for Chat, RAG, Agents, and Actions.
- Onyx Enterprise Edition (EE) includes extra features that are primarily useful for larger organizations.

For feature details, check out [our website](https://www.onyx.app/pricing?utm_source=onyx_repo&utm_medium=github&utm_campaign=readme).

## 👪 Community

Join our open source community on **[Discord](https://discord.gg/TDJ59cGV2X)**!

## 💡 Contributing

Looking to contribute? Please check out the [Contribution Guide](CONTRIBUTING.md) for more details.

---

## File: backend/alembic/README.md

# Alembic DB Migrations

These files are for creating/updating the tables in the Relational DB (Postgres).
Onyx migrations use a generic single-database configuration with an async dbapi.

## To generate new migrations:

From onyx/backend, run:
`alembic revision -m <DESCRIPTION_OF_MIGRATION>`

Note: you cannot use the `--autogenerate` flag as the automatic schema parsing does not work.

Manually populate the upgrade and downgrade in your new migration.

More info can be found here: https://alembic.sqlalchemy.org/en/latest/autogenerate.html

## Running migrations

To run all un-applied migrations:
`alembic upgrade head`

To undo migrations:
`alembic downgrade -X`
where X is the number of migrations you want to undo from the current state

### Multi-tenant migrations

For multi-tenant deployments, you can use additional options:

**Upgrade all tenants:**
```bash
alembic -x upgrade_all_tenants=true upgrade head
```

**Upgrade specific schemas:**
```bash
# Single schema
alembic -x schemas=tenant_12345678-1234-1234-1234-123456789012 upgrade head

# Multiple schemas (comma-separated)
alembic -x schemas=tenant_12345678-1234-1234-1234-123456789012,public,another_tenant upgrade head
```

**Upgrade tenants within an alphabetical range:**
```bash
# Upgrade tenants 100-200 when sorted alphabetically (positions 100 to 200)
alembic -x upgrade_all_tenants=true -x tenant_range_start=100 -x tenant_range_end=200 upgrade head

# Upgrade tenants starting from position 1000 alphabetically
alembic -x upgrade_all_tenants=true -x tenant_range_start=1000 upgrade head

# Upgrade first 500 tenants alphabetically
alembic -x upgrade_all_tenants=true -x tenant_range_end=500 upgrade head
```

**Continue on error (for batch operations):**
```bash
alembic -x upgrade_all_tenants=true -x continue=true upgrade head
```

The tenant range filtering works by:
1. Sorting tenant IDs alphabetically
2. Using 1-based position numbers (1st, 2nd, 3rd tenant, etc.)
3. Filtering to the specified range of positions
4. Non-tenant schemas (like 'public') are always included

---

## File: backend/alembic_tenants/README.md

These files are for public table migrations when operating with multi tenancy.

If you are not a Onyx developer, you can ignore this directory entirely.

---

## File: backend/generated/README.md

- Generated Files
* Generated files live here. This directory should be git ignored.

---

## File: backend/onyx/connectors/README.md

# Writing a new Onyx Connector

This README covers how to contribute a new Connector for Onyx. It includes an overview of the design, interfaces,
and required changes.

Thank you for your contribution!

### Connector Overview

Connectors come in 3 different flows:

- Load Connector:
  - Bulk indexes documents to reflect a point in time. This type of connector generally works by either pulling all
    documents via a connector's API or loads the documents from some sort of a dump file.
- Poll Connector:
  - Incrementally updates documents based on a provided time range. It is used by the background job to pull the latest
    changes and additions since the last round of polling. This connector helps keep the document index up to date
    without needing to fetch/embed/index every document which would be too slow to do frequently on large sets of
    documents.
- Slim Connector:
  - This connector should be a lighter weight method of checking all documents in the source to see if they still exist.
  - This connector should be identical to the Poll or Load Connector except that it only fetches the IDs of the documents, not the documents themselves.
  - This is used by our pruning job which removes old documents from the index.
  - The optional start and end datetimes can be ignored.
- Event Based connectors:
  - Connectors that listen to events and update documents accordingly.
  - Currently not used by the background job, this exists for future design purposes.

### Connector Implementation

Refer to [interfaces.py](https://github.com/onyx-dot-app/onyx/blob/main/backend/onyx/connectors/interfaces.py)
and this first contributor created Pull Request for a new connector (Shoutout to Dan Brown):
[Reference Pull Request](https://github.com/onyx-dot-app/onyx/pull/139)

For implementing a Slim Connector, refer to the comments in this PR:
[Slim Connector PR](https://github.com/onyx-dot-app/onyx/pull/3303/files)

All new connectors should have tests added to the `backend/tests/daily/connectors` directory. Refer to the above PR for an example of adding tests for a new connector.

#### Implementing the new Connector

The connector must subclass one or more of LoadConnector, PollConnector, CheckpointedConnector, or CheckpointedConnectorWithPermSync

The `__init__` should take arguments for configuring what documents the connector will and where it finds those
documents. For example, if you have a wiki site, it may include the configuration for the team, topic, folder, etc. of
the documents to fetch. It may also include the base domain of the wiki. Alternatively, if all the access information
of the connector is stored in the credential/token, then there may be no required arguments.

`load_credentials` should take a dictionary which provides all the access information that the connector might need.
For example this could be the user's username and access token.

Refer to the existing connectors for `load_from_state` and `poll_source` examples. There is not yet a process to listen
for EventConnector events, this will come down the line.

#### Development Tip

It may be handy to test your new connector separate from the rest of the stack while developing.
Follow the below template:

```commandline
if __name__ == "__main__":
    import time
    test_connector = NewConnector(space="engineering")
    test_connector.load_credentials({
        "user_id": "foobar",
        "access_token": "fake_token"
    })
    all_docs = test_connector.load_from_state()

    current = time.time()
    one_day_ago = current - 24 * 60 * 60  # 1 day
    latest_docs = test_connector.poll_source(one_day_ago, current)
```

> Note: Be sure to set PYTHONPATH to onyx/backend before running the above main.

#### Supporting "Include Attachments"

If the source has attachments (files attached to pages, tickets, etc.), let admins opt out of
indexing them by following the shared convention:

- Accept an `include_attachments: bool` kwarg in the connector's `__init__`. The value flows in
  automatically from `connector_specific_config`; no factory or API changes are needed.
  - New connectors should default it to `False`.
  - When retrofitting a connector that already indexes attachments unconditionally, default it to
    `True` — existing connector rows have no `include_attachments` key stored, so the constructor
    default is what they get and their behavior must not change.
- Gate **every** attachment enumeration path on the flag: the main indexing pass **and** the
  slim-doc pass (pruning / permission sync) if the connector has one. The slim pass must admit
  exactly the same documents as the main pass — emitting attachment slim docs the main pass
  skipped leaves permanent `chunk_count IS NULL` rows, while correctly omitting them lets pruning
  clean up previously indexed attachments after an admin turns the setting off.
- In the frontend form, add `buildIncludeAttachmentsOption(<default>)` from
  `web/src/lib/connectors/connectors.tsx` to the connector's `connectorConfigs` entry (use the
  same default as the backend), and add `include_attachments?: boolean` to the connector's config
  interface in that file.

`ConfluenceConnector` and `DrupalWikiConnector` are reference implementations.

### Additional Required Changes:

#### Backend Changes

- Add a new type to
  [DocumentSource](https://github.com/onyx-dot-app/onyx/blob/main/backend/onyx/configs/constants.py)
- Add a mapping from DocumentSource (and optionally connector type) to the right connector class
  [here](https://github.com/onyx-dot-app/onyx/blob/main/backend/onyx/connectors/factory.py#L33)

#### Frontend Changes

- Add the new Connector definition to the `SOURCE_METADATA_MAP` [here](https://github.com/onyx-dot-app/onyx/blob/main/web/src/lib/sources.ts#L59).
- Add the definition for the new Form to the `connectorConfigs` object [here](https://github.com/onyx-dot-app/onyx/blob/main/web/src/lib/connectors/connectors.ts#L79).

#### Docs Changes

Create the new connector page (with guiding images!) with how to get the connector credentials and how to set up the
connector in Onyx. Then create a Pull Request in [https://github.com/onyx-dot-app/documentation](https://github.com/onyx-dot-app/documentation).

### Before opening PR

1. Be sure to fully test changes end to end with setting up the connector and updating the index with new docs from the
   new connector. To make it easier to review, please attach a video showing the successful creation of the connector via the UI (starting from the `Add Connector` page).
2. Add a folder + tests under `backend/tests/daily/connectors` director. For an example, checkout the [test for Confluence](https://github.com/onyx-dot-app/onyx/blob/main/backend/tests/daily/connectors/confluence/test_confluence_basic.py). In the PR description, include a guide on how to setup the new source to pass the test. Before merging, we will re-create the environment and make sure the test(s) pass.
3. Be sure to run the linting/formatting, refer to the formatting and linting section in
   [CONTRIBUTING.md](https://github.com/onyx-dot-app/onyx/blob/main/CONTRIBUTING.md#formatting-and-linting)

---

## File: backend/slackbot_images/README.md

This folder is kept for backwards compatibility with released Slackbot versions that
hardcode GitHub raw URLs under `backend/slackbot_images`.

New Slackbot source icons live in `web/public/slackbot-source-icons`.

TODO(wenxi-onyx): Remove this directory after 2026-07-24, once stable releases have
moved off these legacy GitHub raw URLs.

---

## File: deployment/aws_ecs_fargate/cloudformation/README.md

# Onyx AWS ECS Fargate CloudFormation Deployment

This directory contains CloudFormation templates and scripts to deploy Onyx on AWS ECS Fargate.

## Configuration

All configuration parameters are stored in a single JSON file: `onyx_config.json`. This file contains all the parameters needed for the different CloudFormation stacks.

Example:
```json
{
  "OnyxNamespace": "onyx",
  "Environment": "production",
  "EFSName": "onyx-efs",
  "AWSRegion": "us-east-2",
  "VpcID": "YOUR_VPC_ID",
  "SubnetIDs": "YOUR_SUBNET_ID1,YOUR_SUBNET_ID2",
  "DomainName": "YOUR_DOMAIN e.g ecs.onyx.app",
  "ValidationMethod": "DNS",
  "HostedZoneId": ""
}
```

### Required Parameters

- `Environment`: Used to prefix all stack names during deployment. This is required.
- `OnyxNamespace`: Namespace for the Onyx deployment.
- `EFSName`: Name for the Elastic File System.
- `AWSRegion`: AWS region where resources will be deployed.
- `VpcID`: ID of the VPC where Onyx will be deployed.
- `SubnetIDs`: Comma-separated list of subnet IDs for deployment.
- `DomainName`: Domain name for the Onyx deployment.
- `ValidationMethod`: Method for domain validation (typically "DNS").
- [optional] `HostedZoneId`: Route 53 hosted zone ID (only if using Route 53 for DNS).

The deployment script automatically extracts the needed parameters for each CloudFormation template based on the parameter names defined in the templates.

## Deployment Order

The deployment follows this order:

1. Infrastructure stacks:
   - EFS
   - Cluster
   - ACM

2. Service stacks:
   - Postgres
   - Redis
   - Vespa Engine
   - Model Server (Indexing)
   - Model Server (Inference)
   - Backend API Server
   - Backend Background Server
   - Web Server
   - Nginx

## Usage

To deploy:
```bash
./deploy.sh
```

To uninstall:
```bash
./uninstall.sh
```

---

## File: deployment/docker_compose/README.md

# Welcome to Onyx

To set up Onyx there are several options, Onyx supports the following for deployment:
1. Quick guided install via the install.sh script
2. Pulling the repo and running `docker compose up -d` from the deployment/docker_compose directory
  - Note, it is recommended to copy over the env.template file to .env and edit the necessary values
3. For large scale deployments leveraging Kubernetes, there are two options, Helm or Terraform.

This README focuses on the easiest guided deployment which is via install.sh.

**For more detailed guides, please refer to the documentation: https://docs.onyx.app/deployment/overview**

## install.sh script

```
curl -fsSL https://raw.githubusercontent.com/onyx-dot-app/onyx/main/deployment/docker_compose/install.sh > install.sh && chmod +x install.sh && ./install.sh
```

The script installs the Onyx CLI (`onyx-cli`) and hands over to `onyx-cli deploy install`, which is
where the guided installation lives. Any flags you pass are forwarded to it. If you already have the
CLI (`pip install onyx-cli`), skip the script and run `onyx-cli deploy install` directly.

This provides a guided installation of Onyx via Docker Compose. It will deploy the latest version of Onyx
and set up the volumes to ensure data is persisted across deployments or upgrades.

The deployment files are stored in `~/.config/onyx` (an existing `onyx_data` directory from an older
install is detected and kept in place; `--dir` targets another location). Note that no application
critical data is stored in that directory so even if you delete it, the data needed to restore the app
will not be destroyed.

The data about chats, users, etc. are instead stored as named Docker Volumes. This is managed by Docker
and where it is stored will depend on your Docker setup. You can always delete these as well by running
`onyx-cli deploy uninstall`.

To shut down the deployment without deleting, use `onyx-cli deploy stop`.

### Managing the deployment

Beyond installing, the CLI covers the rest of the lifecycle:

| Command | What it does |
| --- | --- |
| `onyx-cli deploy status` | Installed version, containers, and health (`--json` for scripts) |
| `onyx-cli deploy logs [service...]` | Logs of the deployment's containers |
| `onyx-cli deploy stop` | Stop the containers, keep the data |
| `onyx-cli deploy upgrade [--tag vX.Y.Z]` | Upgrade in place (see below) |
| `onyx-cli deploy uninstall` | Remove the containers, volumes, and deployment directory |

### Upgrading the deployment
Onyx maintains backwards compatibility across all minor versions following SemVer, so upgrading is
`onyx-cli deploy upgrade` (add `--tag vX.Y.Z` to pin a version). It rewrites only IMAGE_TAG, preserves
your .env edits, and backs up hand-edited files.

If you are more comfortable running docker compose commands, you can also run commands directly from
the directory with the docker-compose.yml file. First bring the containers down (`docker compose down`),
verify the version you want in the environment file (see below), (if using `latest` tag, be sure to run
`docker compose pull`) and run `docker compose up` to restart the services on the latest version

### Environment variables
The Docker Compose files try to look for a .env file in the same directory. The installer sets it up
from a file called env.template. Feel free to edit the .env file to customize your deployment. The most
important / common changed values are located near the top of the file. Later `onyx-cli deploy` runs
keep your edits.

IMAGE_TAG is the version of Onyx to run. It is recommended to leave it as latest to get all updates with each redeployment.

Every image publishes a `-dev` twin for each of its tags (e.g. `latest-dev`, `v1.2.3-dev`), so a single
`IMAGE_TAG=latest-dev` selects the dev variant of the whole deployment. Today only the backend image actually differs:
its `-dev` twin adds interactive debugging tools (vim, nano, curl, ps, psql) that the default image leaves out to stay
minimal. The web-server, model-server, and sandbox `-dev` tags are identical to their plain counterparts and exist so
that one version string covers every image.

---

## File: deployment/helm/README.md

# Installing the chart

## Helm repository (default)

```bash
helm repo add onyx https://onyx-dot-app.github.io/onyx
helm repo update
helm install onyx onyx/onyx -n onyx --create-namespace
```

Chart tarballs are attached to GitHub releases named `helm/onyx-<version>`, and
`index.yaml` on the `gh-pages` branch points at them. This is an implementation
detail: the repository URL above does not change and `helm repo add`,
`helm search repo` and `helm upgrade` all work as before.

## OCI registry (alternative)

The same chart is published to GHCR:

```bash
helm install onyx oci://ghcr.io/onyx-dot-app/charts/onyx \
  --version 0.8.16 -n onyx --create-namespace
```

`helm repo add` does not accept `oci://` URLs, so an OCI install needs an
explicit `--version`. There is no `helm repo update` or `helm search repo` for
OCI and no helm command that lists the available versions. Read them from the
`helm/onyx-<version>` [releases](https://github.com/onyx-dot-app/onyx/releases)
or from the Helm repository index:

```bash
helm search repo onyx/onyx --versions   # needs `helm repo add` from above
```

To inspect one version before installing it:

```bash
helm show chart oci://ghcr.io/onyx-dot-app/charts/onyx --version 0.8.16
```

Both channels ship identical bytes from the same build. Use the Helm repository
unless you specifically need OCI.

# Recent chart changes (0.5.0)

If you are upgrading from an earlier 0.4.x release, **read [MIGRATION.md](./MIGRATION.md) first.** The 0.5.0 release dropped the bundled Vespa subchart; chart 0.5.6 ships a guard that fails `helm upgrade` if the legacy `da-vespa` StatefulSet is still in the namespace so you don't lose the indexed data silently.

Other 0.5.0 changes:

* **Redis** — the chart now bundles the `redis-operator` subchart alongside the
  `redis` subchart. The operator installs the CRD that the Redis CR binds to,
  so `helm install` works on a clean cluster without a separate CRD step.
* **PostgreSQL** — a CloudNativePG `Cluster` CR is now rendered from
  `templates/postgres-cluster.yaml`, so `helm install` provisions postgres
  out of the box. The Cluster is named `<release>-pg` (service:
  `<release>-pg-rw`). Set `postgresql.enabled: false` and override
  `configMap.POSTGRES_HOST` if you use an external database (RDS, etc.).
* **REDIS_HOST** — the configmap no longer appends `-master` to the redis
  service name. If you previously overrode `REDIS_HOST` or had clients that
  hard-coded the `-master` suffix, update them to point at the
  redis-operator standalone service.
* **CNPG CRDs** — copied into `crds/cnpg-crds.yaml` so Helm pre-installs
  them before templates. The subchart's own CRD template is disabled
  (`postgresql.crds.create: false`). Run `scripts/check-cnpg-crds.sh` after
  bumping the CNPG subchart version to verify the copy is in sync.
* **Pre-delete hook** — a cleanup Job (`templates/pre-delete-cleanup.yaml`)
  deletes operator-managed CRs before `helm uninstall` tears down the
  operators, ensuring finalizers are processed and namespace cleanup
  completes promptly.

# Dependency updates (when subchart versions are bumped)
* If updating subcharts, you need to run this before committing!
* cd charts/onyx
* helm dependency update .

# Prerequisites

## Redis operator (automatic with bundled Redis)

When `redis.enabled: true` (the chart default), the chart bundles and automatically
installs the `redis-operator` subchart alongside the `redis` subchart. The operator
provides the CRD that the Redis CR binds to, so `helm install` works on a clean
cluster without manual preparation.

**No separate pre-install step is needed** — the chart handles the redis-operator
CRD and controller automatically as part of `helm install onyx onyx/onyx`.

If you don't want the bundled Redis (recommended for production environments using
managed Redis like AWS ElastiCache), see [Using an external Redis](#using-an-external-redis)
below and set `redis.enabled: false` to skip the operator entirely.

## Onyx Craft with Kubernetes sandboxes

When `configMap.ENABLE_CRAFT="true"`, the target cluster must run Kubernetes
`>= 1.33`. Craft Helm deployments provision Kubernetes sandbox pods; Docker
is not a Helm deployment backend for Craft and `configMap.SANDBOX_BACKEND` is
not a bypass for this requirement.

Craft sandbox pods use native restartable init sidecar containers:
`sandbox-init` runs and completes first, `sidecar` starts next as an
`initContainers` entry with `restartPolicy: Always`, and the main `sandbox` app
container starts after the sidecar's startup restore gate is ready.

The chart fails during render/install on older clusters so the incompatibility
is caught before sandbox provisioning. This guard does not apply to non-Craft
installs.

## Using an external Redis

To point Onyx at an externally-managed Redis (e.g. AWS ElastiCache) and skip
the bundled Redis + operator entirely:

```yaml
redis:
  enabled: false

configMap:
  # Override the chart-computed REDIS_HOST. Use the cluster/primary endpoint
  # from your managed Redis. Avoid underscores — they are invalid in K8s DNS
  # labels and look identical to a hung service.
  REDIS_HOST: "<elasticache-primary-endpoint>"
  REDIS_PORT: "6379"
  REDIS_SSL: "true"   # set to "true" if in-transit encryption is enabled

auth:
  redis:
    enabled: true
    # Pre-create a Secret in the release namespace holding the Redis password
    # under the key `redis_password`.
    existingSecret: "elasticache-auth-secret"
    secretKeys:
      REDIS_PASSWORD: redis_password
```

## Sourcing secrets from an external secret manager (ESO)

The chart can render an `ExternalSecret` CR that has [External Secrets
Operator](https://external-secrets.io/) materialize a single Kubernetes Secret
from an upstream provider (AWS Secrets Manager, GCP Secret Manager, Vault,
etc.). All `auth.*` sections then read their per-section keys from that
materialized Secret, and any loose env-var secrets are projected into pods
via `envFrom`.

**Prerequisite:** the chart does *not* install ESO. Install it separately and
create a `SecretStore` or `ClusterSecretStore` (e.g. `aws-secrets-manager`)
before enabling `externalSecret`. See https://external-secrets.io/ for setup.

The upstream secret should be a JSON object whose top-level keys map 1:1 to
the keys the chart expects in the materialized Secret (the union of every
`auth.<section>.secretKeys` value plus any loose env-var names you project via
`extraEnvFromSecret`).

```yaml
externalSecret:
  enabled: true
  # Identifier of the upstream secret (e.g. AWS Secrets Manager secret name).
  refPath: "onyx/<customer>/app-secrets"
  # Name of the materialized k8s Secret. Point auth.*.existingSecret here.
  secretName: onyx-app-secrets
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  refreshInterval: 1h

# Inject every key from onyx-app-secrets as an env var on backend pods.
# Use for secrets that today live as plaintext in `configMap:` blocks.
extraEnvFromSecret: onyx-app-secrets

auth:
  postgresql:
    existingSecret: onyx-app-secrets
    secretKeys:
      POSTGRES_USER: postgres_user
      POSTGRES_PASSWORD: postgres_password
  redis:
    existingSecret: onyx-app-secrets
    secretKeys:
      REDIS_PASSWORD: redis_password
  # ...repeat for any other auth section that should read from the shared Secret.
```

When `externalSecret.enabled: false` (the default) the chart renders exactly
as before — no `ExternalSecret` CR is created and `envFrom` is unchanged.

### First-install reconciliation window

The Helm release renders both the `ExternalSecret` CR and the Deployments that
reference the materialized Secret in the same apply. ESO reconciles the CR
asynchronously, typically within a few seconds. During that brief window,
backend pods will be in `CreateContainerConfigError` because their
`envFrom: secretRef` references a Secret that does not yet exist. The kubelet
retries pod creation automatically; once ESO materializes the Secret, pods
transition to `Running` without manual intervention.

We intentionally leave the `secretRef` non-optional. Marking it optional would
allow pods to boot with empty env vars and fail later in a way that is harder
to diagnose; the brief `CreateContainerConfigError` is the clearer failure
mode if anything is misconfigured (wrong SM path, missing IRSA permissions,
missing keys in the upstream blob).

If you need pods to be `Ready` before traffic is routed, rely on the existing
readiness probes — they will not pass until the env vars are present.

### Model-server pods

`indexing-model-deployment` and `inference-model-deployment` deliberately do
**not** consume `extraEnvFromSecret`. Model servers only need model-config
env vars (already in the chart's `configMap`), so injecting application-level
secrets like `POSTGRES_PASSWORD` would needlessly widen their secret-exposure
surface.

# Values that come from docker-compose (do not copy them)

The Onyx docker-compose stack uses service-name hostnames like `api_server`,
`inference_model_server`, and `cache`. Those names contain underscores, which
are **invalid in Kubernetes DNS labels** — DNS lookups for them will fail and
the symptom is often a blank login page or `TypeError: fetch failed` in web
pod logs.

The chart computes correct K8s service hostnames for you. If you are
adapting an `.env` file from docker-compose, **remove** these keys from your
Helm `configMap:` and let the chart fill them in:

| Key | Don't set to (docker-compose) | Chart-computed default |
| --- | --- | --- |
| `REDIS_HOST` | `cache` | `redis.redisStandalone.name | default <release>` |
| `INTERNAL_URL` | `http://api_server:8080` | `http://<release>-api-service:8080` |
| `API_SERVER_HOST` | `api_server` | computed |
| `MODEL_SERVER_HOST` | `inference_model_server` | `<release>-inference-model-service` |
| `INDEXING_MODEL_SERVER_HOST` | `indexing_model_server` | `<release>-indexing-model-service` |

Other docker-compose-style values you should set deliberately:

* `DOMAIN` should be your public hostname (e.g. `onyx.example.com`), not
  `localhost`. It affects cookies and CORS.
* `WEB_DOMAIN` should be the full origin (e.g. `https://onyx.example.com`).
  Watch for typos like `hhttps://...`; they silently break email links and
  OAuth redirects.

# Local testing

> This section covers chart-maintainer testing; for the Onyx Craft local-kind developer workflow, see [docs/craft/dev/local-kubernetes.md](/docs/craft/dev/local-kubernetes.md).

## One time setup
* brew install kind
* Ensure you have no config at ~/.kube/config
* kind create cluster
* mv ~/.kube/config ~/.kube/kind-config

## Automated install and test with ct
* export KUBECONFIG=~/.kube/kind-config
* kubectl config use-context kind-kind
* from source root run the following. This does a very basic test against the web server
  * ct install --all --helm-extra-set-args="--set=nginx.enabled=false" --debug --config ct.yaml

## Output template to file and inspect
* cd charts/onyx
* helm template test-output . --set auth.opensearch.values.opensearch_admin_password='StrongPassword123!' > test-output.yaml
* Craft Kubernetes sandbox version guard check:
  * expect failure:
    `helm template test-output . -f values-ci.yaml --kube-version 1.32.0 --show-only templates/craft-kubernetes-version-check.yaml`
  * expect success:
    `helm template test-output . -f values-ci.yaml --kube-version 1.33.0 --show-only templates/craft-kubernetes-version-check.yaml`

## Test the entire cluster manually
* cd charts/onyx
* helm install onyx . -n onyx --set postgresql.primary.persistence.enabled=false --set auth.opensearch.values.opensearch_admin_password='StrongPassword123!'
  * the postgres flag is to keep the storage ephemeral for testing. You probably don't want to set that in prod.
  * the OpenSearch admin password must be set on first install unless you are supplying `auth.opensearch.existingSecret`.
  * no flag for ephemeral vespa storage yet, might be good for testing
* kubectl -n onyx port-forward service/onyx-nginx 8080:80
  * this will forward the local port 8080 to the installed chart for you to run tests, etc.
* When you are finished
  * helm uninstall onyx -n onyx
  * Vespa leaves behind a PVC. Delete it if you are completely done.
    * k -n onyx get pvc
    * k -n onyx delete pvc vespa-storage-da-vespa-0
  * If you didn't disable Postgres persistence earlier, you may want to delete that PVC too.

## Run as non-root user
By default, some onyx containers run as root. If you'd like to explicitly run the onyx containers as a non-root user, update the values.yaml file for the following components:
  * `celery_shared`, `api`, `webserver`, `indexCapability`, `inferenceCapability`
    ```yaml
    securityContext:
      runAsNonRoot: true
      runAsUser: 1001
    ```
  * `vespa`
    ```yaml
    podSecurityContext:
      fsGroup: 1000
    securityContext:
      privileged: false
      runAsUser: 1000
    ```

## Resourcing
In the helm charts, we have resource suggestions for all Onyx-owned components. 
These are simply initial suggestions, and may need to be tuned for your specific use case.

Please talk to us in Slack if you have any questions!

## Autoscaling options
The chart renders Kubernetes HorizontalPodAutoscalers by default. To keep this behavior, leave
`autoscaling.engine` as `hpa` and adjust the per-component `autoscaling.*` values as needed.

If you would like to use KEDA ScaledObjects instead:

1. Install and manage the KEDA operator in your cluster yourself (for example via the official KEDA Helm chart). KEDA is no longer packaged as a dependency of the Onyx chart.
2. Set `autoscaling.engine: keda` in your `values.yaml` and enable autoscaling for the components you want to scale.

When `autoscaling.engine` is set to `keda`, the chart will render the existing ScaledObject templates; otherwise HPAs will be rendered.

---

## File: deployment/README.md

Documentation for how to deploy Onyx can be found in our official docs:
https://docs.onyx.app/deployment/overview

## Maintaining the compose files (contributors)

`docker_compose/docker-compose.yml`, `docker-compose.prod.yml` and
`docker-compose.prod-no-letsencrypt.yml` are generated from the single source of truth
`docker_compose/docker-compose.template.yml` — do not hand-edit them. To change any of the three,
edit the template (per-variant differences are expressed with `#!for` / `#!only` / `#!value`
directives, documented in `ods generate-compose --help`) and regenerate:

```
ods generate-compose --write
```

The generator lives in `tools/ods` (`internal/composegen`) and ships with the `onyx-devtools`
package. The `docker-compose-sync` pre-commit hook runs it automatically for commits touching the
template or the generated files, so a stray edit to a generated file gets reverted on the next
commit.

onyx-cli embeds copies of the guided-install deployment files (the generated
`docker-compose.yml` and `docker-compose.prod.yml`, the lite/craft overlays, the env templates,
the nginx config, and `docker_compose/README.md`) under
`cli/internal/deploy/deployfiles/embedded/`. The same `ods generate-compose --write` run refreshes
them after rendering the variants, and a drift test in the cli module (`go test ./...`) gates
staleness. If you change any of those files, re-run the generator and commit the refreshed embedded
copies.

Note that `docker_compose/README.md` ships inside every self-hosted install, so keep it
user-facing — contributor notes like this one belong here instead.