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

# Kubernetes (Helm)

> Deploy nao on a Kubernetes cluster with the official Helm chart

nao ships a Helm chart for running the container on Kubernetes. It covers the deployment, service, secrets, an optional PostgreSQL subchart, persistent volumes for context and projects, and optional autoscaling and pod disruption budgets.

The chart lives in [`helm/`](https://github.com/getnao/nao/tree/main/helm) in the nao repository and is published to GHCR.

## Install

```bash theme={null}
helm install nao oci://ghcr.io/getnao/nao/charts/nao --version 0.1.0 \
  --namespace nao --create-namespace \
  --set secrets.betterAuthSecret="$(openssl rand -base64 32)"
```

`--version` is the **chart** version. The nao image tag comes from the chart's `appVersion`; override it with `--set image.tag=<tag>` to run a different nao release without waiting for a chart release.

To install from a clone of the repository instead:

```bash theme={null}
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm dependency update ./helm

helm install nao ./helm \
  --namespace nao --create-namespace \
  --set secrets.betterAuthSecret="$(openssl rand -base64 32)"
```

## Choose a context mode

`config.contextSource` picks where the project comes from. It maps to [`NAO_CONTEXT_SOURCE`](/nao-agent/self-hosting/context-source).

<Tabs>
  <Tab title="local (default)">
    The project directory is mounted from a PersistentVolume. Pre-populate the PVC with a valid nao project - a directory containing `nao_config.yaml`.

    ```yaml theme={null}
    config:
      contextSource: local
      contextPath: /app/project

    persistence:
      enabled: true
      size: 1Gi
      # existingClaim: my-nao-context-pvc
    ```
  </Tab>

  <Tab title="git">
    The project is cloned from a repository at pod startup, and optionally pulled again on a schedule.

    ```yaml theme={null}
    config:
      contextSource: git
      contextPath: /app/project
      contextGitUrl: https://github.com/your-org/your-nao-project.git
      contextGitBranch: main
      refreshSchedule: "0 * * * *"   # optional, pull every hour

    secrets:
      contextGitToken: ghp_...        # required for private repositories
    ```
  </Tab>

  <Tab title="api">
    Projects are pushed dynamically with [`nao deploy`](/nao-agent/cloud/deploy). A writable volume is created at `/app/projects`.

    ```yaml theme={null}
    config:
      contextSource: api

    projectsPersistence:
      enabled: true
      size: 5Gi
    ```
  </Tab>
</Tabs>

## Database

The chart deploys a bundled `bitnami/postgresql` by default. To point at your own instance, disable it and set a URI:

```yaml theme={null}
postgresql:
  enabled: false

secrets:
  dbUri: "postgres://user:password@my-postgres-host:5432/nao"
```

SQLite works for single-node or testing deployments (`dbUri: "sqlite:./db.sqlite"`), but use PostgreSQL for anything real.

<Warning>
  `postgresql.auth.password` is empty by default. Set it before going to production, and set `secrets.betterAuthSecret` to a value you generated yourself.
</Warning>

## Ingress

Set `ingress.enabled=true` to expose nao through an Ingress controller. At least one host with one path is required. Point `config.betterAuthUrl` (and any SSO redirect URIs) at the public URL the ingress serves.

```yaml theme={null}
ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: nao.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: nao-tls
      hosts:
        - nao.example.com
```

## External secrets and extra env vars

By default the chart renders a Secret from the `secrets.*` values. To load secrets from a Secret you manage yourself (GitOps, External Secrets Operator, Sealed Secrets), set `existingSecret` to its name. The chart then renders no Secret of its own and the whole `secrets.*` block is ignored, so the referenced Secret must carry the same keys (`BETTER_AUTH_SECRET`, `DB_URI`, provider API keys, and so on).

```yaml theme={null}
existingSecret: my-nao-secret
```

<Note>
  The pod-template checksum only tracks the chart-rendered Secret, so rotating an external Secret does not restart pods on its own. Pair it with a rollout trigger (stakater/reloader, ESO templated annotations) or run `kubectl rollout restart` after rotation.
</Note>

Inject any environment variable the chart has no first-class key for with `extraEnv` (appended to the container) or `extraEnvFrom` (extra `configMapRef` / `secretRef` sources):

```yaml theme={null}
extraEnv:
  - name: NAO_STORAGE_BACKEND
    value: s3
extraEnvFrom:
  - secretRef:
      name: my-extra-secret
```

## Values reference

| Key                                        | Default                   | Description                                                                                                        |
| ------------------------------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `replicaCount`                             | `1`                       | Number of pod replicas                                                                                             |
| `image.repository`                         | `getnao/nao`              | Container image repository                                                                                         |
| `image.tag`                                | `""` (chart `appVersion`) | Image tag                                                                                                          |
| `image.pullPolicy`                         | `IfNotPresent`            | Image pull policy                                                                                                  |
| `config.serverPort`                        | `"5005"`                  | Port the backend listens on                                                                                        |
| `config.betterAuthUrl`                     | `"http://localhost:5005"` | Public URL used for auth callbacks                                                                                 |
| `config.contextSource`                     | `"local"`                 | Context mode: `local`, `git`, or `api`                                                                             |
| `config.contextPath`                       | `"/app/project"`          | Mount path for the nao project                                                                                     |
| `config.contextGitUrl`                     | `""`                      | Repository URL (git mode)                                                                                          |
| `config.contextGitBranch`                  | `"main"`                  | Branch to clone (git mode)                                                                                         |
| `config.refreshSchedule`                   | `""`                      | Cron expression for a periodic git pull                                                                            |
| `config.dbSsl`                             | `false`                   | Require TLS on the database connection                                                                             |
| `config.enableUserLogin`                   | `true`                    | Email and password login                                                                                           |
| `config.enableUserSignup`                  | `false`                   | Allow self sign-up                                                                                                 |
| `config.defaultUserRole`                   | `"user"`                  | Role for new users: `admin`, `user`, or `viewer`                                                                   |
| `config.githubSso`                         | `false`                   | Enable "Sign in with GitHub"                                                                                       |
| `config.gitlabSso`                         | `false`                   | Enable "Sign in with GitLab"                                                                                       |
| `config.gitlabBaseUrl`                     | `""`                      | Self-hosted GitLab instance URL                                                                                    |
| `config.betaAutomationsEnabled`            | `true`                    | [Automations](/nao-agent/chat/capabilities/automations)                                                            |
| `config.betaContextRecommendationsEnabled` | `false`                   | [Recommendations](/nao-agent/context-engineering/recommendations)                                                  |
| `existingSecret`                           | `""`                      | Load all secret env vars from a Secret you manage instead of rendering one (the `secrets.*` block is then ignored) |
| `extraEnv`                                 | `[]`                      | Extra env vars appended verbatim to the nao container                                                              |
| `extraEnvFrom`                             | `[]`                      | Extra `envFrom` sources appended after the chart's ConfigMap and Secret                                            |
| `secrets.betterAuthSecret`                 | `""`                      | **Required.** Auth session secret                                                                                  |
| `secrets.openaiApiKey`                     | `""`                      | OpenAI API key                                                                                                     |
| `secrets.anthropicApiKey`                  | `""`                      | Anthropic API key                                                                                                  |
| `secrets.naoLicense`                       | `""`                      | Enterprise license, required for the SSO providers                                                                 |
| `secrets.redisUrl`                         | `""`                      | Redis connection string                                                                                            |
| `secrets.dbUri`                            | `""`                      | Database URI (ignored when `postgresql.enabled=true`)                                                              |
| `secrets.contextGitToken`                  | `""`                      | Git token for private repositories                                                                                 |
| `service.type`                             | `ClusterIP`               | Kubernetes service type                                                                                            |
| `service.port`                             | `80`                      | Service port                                                                                                       |
| `ingress.enabled`                          | `false`                   | Expose nao through an Ingress controller                                                                           |
| `resources.requests`                       | `500m` CPU, `512Mi`       | Requests                                                                                                           |
| `resources.limits`                         | `2` CPU, `2Gi`            | Limits                                                                                                             |
| `autoscaling.enabled`                      | `false`                   | Enable the HorizontalPodAutoscaler (`minReplicas` 1, `maxReplicas` 5)                                              |
| `podDisruptionBudget.enabled`              | `false`                   | Enable the PodDisruptionBudget                                                                                     |
| `persistence.enabled`                      | `false`                   | Context PVC (local mode)                                                                                           |
| `projectsPersistence.enabled`              | `false`                   | Projects PVC (api mode)                                                                                            |
| `postgresql.enabled`                       | `true`                    | Deploy the bundled PostgreSQL                                                                                      |

The chart's [`values.yaml`](https://github.com/getnao/nao/blob/main/helm/values.yaml) is the full list. Any nao environment variable not exposed as a value can still be set through the chart's config and secret templates.

<Note>
  Running more than one replica, permanent storage must be shared across pods: use the S3 backend, or a read-write-many volume. See [Files and Storage](/nao-agent/chat/capabilities/files-and-storage#configuring-storage).
</Note>

## Operate

```bash theme={null}
helm upgrade nao ./helm --namespace nao -f my-values.yaml   # upgrade
helm history nao --namespace nao                            # revisions
helm rollback nao <revision> --namespace nao                # roll back
helm test nao --namespace nao                               # connection test
```

## Next steps

<CardGroup cols={2}>
  <Card title="Deployment guide" icon="docker" href="/nao-agent/self-hosting/deployment-guide">
    Environment variables and the full self-hosting walkthrough
  </Card>

  <Card title="Deployment-managed context" icon="code-branch" href="/nao-agent/self-hosting/context-source">
    How `NAO_CONTEXT_SOURCE` resolves the project at startup
  </Card>
</CardGroup>
