Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
69c3390
feat: add Helm chart for Kubernetes deployment
mfournioux Jul 8, 2026
e1bd2e4
feat: add Helm chart for Kubernetes deployment
mfournioux Jul 8, 2026
a83ca00
Remove betterAuthUrl from helm install command
mfournioux Jul 8, 2026
4cd177b
Remove link to nao GitHub repository in README
mfournioux Jul 8, 2026
b04b481
Update helm/README.md
mfournioux Jul 8, 2026
34b47ee
Update helm/templates/tests/test-connection.yaml
mfournioux Jul 8, 2026
bc4e4e7
Update helm/values.yaml
mfournioux Jul 8, 2026
23f2896
fix: secure git-sync credential handling to prevent token leakage in …
mfournioux Jul 8, 2026
b2be4f7
fix: add BETTER_AUTH_URL default to values.yaml matching backend zod …
mfournioux Jul 8, 2026
7bdf0b0
fix: correct DB_URI printf format in _helpers.tpl
mfournioux Jul 8, 2026
d1074ea
fix: add commonAnnotations to ServiceAccount and Service templates
mfournioux Jul 8, 2026
3a710af
fix: add git clean after git reset in git-sync initContainer
mfournioux Jul 8, 2026
f410580
fix: add checksum annotations to Deployment for ConfigMap/Secret changes
mfournioux Jul 8, 2026
fe11323
fix: remove entire context directory before git clone to avoid hidden…
mfournioux Jul 8, 2026
6e66c61
fix: set NAO_PROJECTS_DIR for api contextSource mode
mfournioux Jul 8, 2026
6dc858b
docs: document allowInsecureImages as temporary migration setting
mfournioux Jul 8, 2026
75bf2ff
fix: remove duplicate secrets.contextGitToken row from README table
mfournioux Jul 8, 2026
ab53f42
docs: add missing LLM base URLs and gitSync.image to ARCHITECTURE.md …
mfournioux Jul 8, 2026
bd1c2dd
docs: remove stale NAO_LICENSE reference from ARCHITECTURE.md
mfournioux Jul 8, 2026
dbf9439
fix: remove dead git pull code from git-sync initContainer
mfournioux Jul 8, 2026
03c18bf
Update helm/values.yaml
mfournioux Jul 8, 2026
3c1b52c
Merge branch 'getnao:main' into feat/helm-chart
mfournioux Jul 8, 2026
c085455
fix: preserve PVC mount point in git-sync initContainer
mfournioux Jul 8, 2026
8662e7d
fix: correct annotation merge precedence in Service and ServiceAccount
mfournioux Jul 8, 2026
7a559f4
fix: remove required guard from DB_URI helper, add existingSecret sup…
mfournioux Jul 8, 2026
cca7931
Update helm/templates/deployment.yaml
mfournioux Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions helm/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Patterns to ignore when building packages.
.DS_Store
.git/
.gitignore
.vscode/
.idea/
*.swp
*.bak
*.tmp
472 changes: 472 additions & 0 deletions helm/ARCHITECTURE.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions helm/Chart.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies:
- name: postgresql
repository: https://charts.bitnami.com/bitnami
version: 16.7.14
digest: sha256:3d12513cb378249e854df4b9edc49e63c5ab55842efdcc1b3ca6512b371ac811
generated: "2026-04-28T16:34:47.673475258+02:00"
29 changes: 29 additions & 0 deletions helm/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: v2
name: nao
description: Helm chart for nao — an open-source analytics agent that transforms natural language into SQL queries.
type: application
version: 0.1.0
appVersion: "latest"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The default image tag resolves to :latest (via appVersion: "latest" defaulted into image.tag), which is a mutable tag. This means the same chart version can pull a different image after a pod restart or node re-schedule with no chart version change, making rollbacks unreliable and deployments unreproducible. Since this is the official Helm chart for deploying nao as a managed service, a specific default tag (e.g., the latest stable release) would be more appropriate. Consider either pinning appVersion to a specific release tag or adding a defaultTag entry in values.yaml so the chart version and image tag are independently manageable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helm/Chart.yaml, line 6:

<comment>The default image tag resolves to `:latest` (via `appVersion: "latest"` defaulted into `image.tag`), which is a mutable tag. This means the same chart version can pull a different image after a pod restart or node re-schedule with no chart version change, making rollbacks unreliable and deployments unreproducible. Since this is the official Helm chart for deploying nao as a managed service, a specific default tag (e.g., the latest stable release) would be more appropriate. Consider either pinning `appVersion` to a specific release tag or adding a `defaultTag` entry in values.yaml so the chart version and image tag are independently manageable.</comment>

<file context>
@@ -0,0 +1,29 @@
+description: Helm chart for nao — an open-source analytics agent that transforms natural language into SQL queries.
+type: application
+version: 0.1.0
+appVersion: "latest"
+
+keywords:
</file context>


keywords:
- analytics
- ai
- sql
- text-to-sql
- agent

home: https://github.com/mfournioux/nao
sources:
- https://github.com/mfournioux/nao

maintainers:
- name: Maxime FOURNIOUX
url: https://github.com/mfournioux/nao

kubeVersion: ">=1.24.0"

dependencies:
- name: postgresql
version: "16.7.14"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
203 changes: 203 additions & 0 deletions helm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# nao Helm Chart

Helm chart for deploying nao — an open-source analytics agent that transforms natural language into SQL queries — on Kubernetes.

## Prerequisites

- (Optional) A running PostgreSQL instance, or enable the bundled bitnami/postgresql subchart

## Chart structure

```
helm/
├── Chart.yaml # Chart metadata & bitnami/postgresql dependency
├── values.yaml # Default configuration values
├── .helmignore
└── templates/
├── NOTES.txt # Post-install instructions
├── _helpers.tpl # Template helpers
├── configmap.yaml # Non-sensitive environment variables
├── secret.yaml # Sensitive values (API keys, auth secret, DB URI)
├── serviceaccount.yaml
├── deployment.yaml # Main nao workload
├── service.yaml
├── pvc.yaml # Conditional (local / api context modes)
├── hpa.yaml # Conditional
├── pdb.yaml # Conditional
└── tests/
└── test-connection.yaml # helm test pod
```

## Installation

### 1. Add the bitnami repository (for the PostgreSQL dependency)

```bash
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
```

### 2. Install dependencies

```bash
helm dependency update ./helm
```

### 3. Install the chart

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

## Context modes

nao supports three ways to load its project configuration, controlled by `config.contextSource`.

### `local` (default)

The nao project directory is mounted as a Kubernetes PersistentVolume.

```yaml
# values-local.yaml
config:
contextSource: local
contextPath: /app/project

persistence:
enabled: true
size: 1Gi
# existingClaim: my-nao-context-pvc # use an existing PVC
```

> **Note:** You must pre-populate the PVC with a valid nao project (a directory containing `nao_config.yaml`).

### `git`

The project is cloned from a Git repository at pod startup.

```yaml
# values-git.yaml
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
```

### `api`

Projects are deployed dynamically via the `nao deploy` CLI. A writable volume is created at `/app/projects`.

```yaml
# values-api.yaml
config:
contextSource: api

projectsPersistence:
enabled: true
size: 5Gi
```

## Values reference

| Key | Default | Description |
|-----|---------|-------------|
| `replicaCount` | `1` | Number of pod replicas |
| `image.repository` | `ghcr.io/getnao/nao` | Container image repository |
| `image.tag` | `""` (→ appVersion) | Image tag |
| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
| `config.serverPort` | `"5005"` | Fastify backend listening port |
| `config.betterAuthUrl` | `"http://localhost:5005"` | Public URL for auth callbacks |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Inconsistent config.betterAuthUrl default across documentation files. The README values reference lists the default as "http://localhost:5005", while the ARCHITECTURE reference lists it as "". Neither matches the actual values.yaml — config.betterAuthUrl is not defined at all, so the Helm default is empty string, which produces BETTER_AUTH_URL="" in the ConfigMap. Align both docs to the actual default and consider whether the value should be added to values.yaml with a sensible default.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helm/README.md, line 117:

<comment>Inconsistent `config.betterAuthUrl` default across documentation files. The README values reference lists the default as `"http://localhost:5005"`, while the ARCHITECTURE reference lists it as `""`. Neither matches the actual values.yaml — `config.betterAuthUrl` is not defined at all, so the Helm default is empty string, which produces `BETTER_AUTH_URL=""` in the ConfigMap. Align both docs to the actual default and consider whether the value should be added to values.yaml with a sensible default.</comment>

<file context>
@@ -0,0 +1,204 @@
+| `image.tag` | `""` (→ appVersion) | Image tag |
+| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
+| `config.serverPort` | `"5005"` | Fastify backend listening port |
+| `config.betterAuthUrl` | `"http://localhost:5005"` | Public URL for auth callbacks |
+| `config.contextSource` | `"local"` | Context mode: `local` \| `git` \| `api` |
+| `config.contextPath` | `"/app/project"` | Mount path for the nao project |
</file context>

| `config.contextSource` | `"local"` | Context mode: `local` \| `git` \| `api` |
| `config.contextPath` | `"/app/project"` | Mount path for the nao project |
| `config.contextGitUrl` | `""` | Git repo URL (git mode) |
| `config.contextGitBranch` | `"main"` | Git branch to clone (git mode) |
| `config.refreshSchedule` | `""` | Cron for periodic git pull |
| `secrets.betterAuthSecret` | `""` | **Required.** Auth session secret |
| `secrets.openaiApiKey` | `""` | OpenAI API key |
| `secrets.anthropicApiKey` | `""` | Anthropic API key |
| `secrets.dbUri` | `""` | Database URI (overridden when `postgresql.enabled=true`) |
| `secrets.contextGitToken` | `""` | Git token for private repos |
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
| `service.type` | `ClusterIP` | Kubernetes service type |
| `service.port` | `80` | Service port |
| `resources.requests.cpu` | `500m` | CPU request |
| `resources.requests.memory` | `512Mi` | Memory request |
| `resources.limits.cpu` | `2` | CPU limit |
| `resources.limits.memory` | `2Gi` | Memory limit |
| `autoscaling.enabled` | `false` | Enable HPA |
| `autoscaling.minReplicas` | `1` | Minimum replicas |
| `autoscaling.maxReplicas` | `5` | Maximum replicas |
| `podDisruptionBudget.enabled` | `false` | Enable PDB |
| `persistence.enabled` | `false` | Enable context PVC (local mode) |
| `persistence.size` | `1Gi` | Context PVC size |
| `persistence.existingClaim` | `""` | Use an existing PVC |
| `projectsPersistence.enabled` | `false` | Enable projects PVC (api mode) |
| `projectsPersistence.size` | `5Gi` | Projects PVC size |
| `postgresql.enabled` | `true` | Deploy bundled PostgreSQL |
| `postgresql.auth.database` | `nao` | PostgreSQL database name |
| `postgresql.auth.username` | `nao` | PostgreSQL username |
| `postgresql.auth.password` | `""` | PostgreSQL password (**change in production**) |

For the full list of values and their descriptions, see [`values.yaml`](./values.yaml).

## Using an external database

Set `postgresql.enabled=false` and provide a connection URI:

```yaml
postgresql:
enabled: false

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

SQLite is also supported for single-node / testing deployments:

```yaml
postgresql:
enabled: false

secrets:
dbUri: "sqlite:./db.sqlite"
```

## Upgrade

```bash
helm upgrade nao ./helm --namespace nao -f my-values.yaml
```

## Rollback

```bash
# List revision history
helm history nao --namespace nao

# Roll back to a previous revision
helm rollback nao <revision> --namespace nao
```

## Running chart tests

```bash
helm test nao --namespace nao
```

## Uninstall

```bash
helm uninstall nao --namespace nao
```

> **Note:** PersistentVolumeClaims are not deleted automatically. Remove them manually if no longer needed:
> ```bash
> kubectl delete pvc -l app.kubernetes.io/instance=nao --namespace nao
> ```
Binary file added helm/charts/postgresql-16.7.14.tgz
Binary file not shown.
24 changes: 24 additions & 0 deletions helm/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Thank you for installing {{ .Chart.Name }} {{ .Chart.AppVersion }}.

Release name : {{ .Release.Name }}
Namespace : {{ .Release.Namespace }}

To access nao locally, run:

kubectl port-forward --namespace {{ .Release.Namespace }} \
svc/{{ include "nao.fullname" . }} 5005:{{ .Values.service.port }}

Then open http://127.0.0.1:5005 in your browser.

Context mode: {{ .Values.config.contextSource }}
{{- if eq .Values.config.contextSource "local" }}
→ Make sure a PVC containing a valid nao project is mounted at {{ .Values.config.contextPath }}.
{{- else if eq .Values.config.contextSource "git" }}
→ The project will be cloned from {{ .Values.config.contextGitUrl }} (branch: {{ .Values.config.contextGitBranch }}).
{{- else if eq .Values.config.contextSource "api" }}
→ Deploy projects via: nao deploy --url http://<nao-host>
{{- end }}

To check the release status:
helm status {{ .Release.Name }} --namespace {{ .Release.Namespace }}
helm get all {{ .Release.Name }} --namespace {{ .Release.Namespace }}
109 changes: 109 additions & 0 deletions helm/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "nao.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this.
*/}}
{{- define "nao.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "nao.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "nao.labels" -}}
helm.sh/chart: {{ include "nao.chart" . }}
{{ include "nao.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Common annotations — applied at metadata.annotations on top-level objects
(Deployment, StatefulSet, etc.), as required by org-wide admission policies.
*/}}
{{- define "nao.annotations" -}}
{{- with .Values.commonAnnotations }}
{{- toYaml . }}
{{- end }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "nao.selectorLabels" -}}
app.kubernetes.io/name: {{ include "nao.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Service account name
*/}}
{{- define "nao.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "nao.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Full image reference (repository:tag)
*/}}
{{- define "nao.image" -}}
{{- $tag := .Values.image.tag | default .Chart.AppVersion -}}
{{- printf "%s:%s" .Values.image.repository $tag -}}
{{- end }}

{{/*
PostgreSQL host — uses the bitnami subchart service name when postgresql.enabled=true,
otherwise falls back to a user-supplied host embedded in secrets.dbUri.
*/}}
{{- define "nao.postgresqlHost" -}}
{{- if .Values.postgresql.enabled -}}
{{- printf "%s-postgresql" (include "nao.fullname" .) -}}
{{- end -}}
{{- end }}
{{/*
Build the DB_URI from subchart values when postgresql.enabled=true.
Does not support auth.existingSecret — when using existingSecret,
set secrets.dbUri manually with the full connection string.
*/}}
{{- define "nao.dbUri" -}}
{{- if .Values.postgresql.enabled -}}
{{- if not .Values.postgresql.auth.existingSecret }}
{{- printf "postgres://%s:%s@%s:5432/%s"
.Values.postgresql.auth.username
.Values.postgresql.auth.password
(include "nao.postgresqlHost" .)
.Values.postgresql.auth.database -}}
{{- else }}
{{- .Values.secrets.dbUri }}
{{- end }}
{{- else -}}
{{- .Values.secrets.dbUri -}}
{{- end -}}
{{- end }}
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Loading
Loading