feat: add Helm chart for Kubernetes deployment#1100
Conversation
|
This PR was auto-closed. Only contributors approved with Maintainers review auto-closed issues daily. Issues that do not meet the quality bar in CONTRIBUTING.md will not be reopened or receive a reply. If a maintainer replies See CONTRIBUTING.md. |
Removed betterAuthUrl configuration from helm install command. Signed-off-by: Maxime Fournioux <55544262+mfournioux@users.noreply.github.com>
Signed-off-by: Maxime Fournioux <55544262+mfournioux@users.noreply.github.com>
There was a problem hiding this comment.
17 issues found across 18 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="helm/templates/deployment.yaml">
<violation number="1" location="helm/templates/deployment.yaml:54">
P2: Context refreshes can leave stale files from previous revisions because `git reset --hard` does not delete untracked files in the persistent checkout. Adding `git clean -fdx` after the reset keeps the mounted project identical to the requested Git revision.</violation>
<violation number="2" location="helm/templates/deployment.yaml:57">
P2: Reinitializing a non-Git PVC can fail when hidden files already exist because `rm -rf .../*` leaves dotfiles behind before cloning into that directory. Removing all children with `find` avoids a "destination path already exists and is not an empty directory" startup failure.</violation>
<violation number="3" location="helm/templates/deployment.yaml:86">
P2: Configuration and secret changes may not reach running pods after `helm upgrade` because the Deployment template does not change when only the ConfigMap or Secret data changes. Adding checksum annotations for those templates to `spec.template.metadata.annotations` would trigger an automatic rollout for updated env values.</violation>
</file>
<file name="helm/README.md">
<violation number="1" location="helm/README.md:117">
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.</violation>
<violation number="2" location="helm/README.md:127">
P3: Duplicate `secrets.contextGitToken` row in the values reference table — the same entry appears twice consecutively. Remove the duplicate row to keep the table clean and unambiguous.</violation>
</file>
<file name="helm/templates/configmap.yaml">
<violation number="1" location="helm/templates/configmap.yaml:19">
P2: `contextSource=api` renders an environment value the Python context provider rejects, so FastAPI context endpoints report errors instead of representing the API-mode projects volume. The chart should either map API mode to supported runtime env vars or add runtime support before advertising/rendering `NAO_CONTEXT_SOURCE=api`.</violation>
</file>
<file name="helm/values.yaml">
<violation number="1" location="helm/values.yaml:29">
P2: Production installs default to an archived PostgreSQL image with Helm image verification disabled, so users inherit an unpatched database image unless they override values. It would be safer to use a supported image path by default or make `bitnamilegacy`/`allowInsecureImages` an explicit opt-in migration setting.</violation>
<violation number="2" location="helm/values.yaml:85">
P1: Default Helm installs can start with an invalid `BETTER_AUTH_URL`, causing backend env validation to fail before the service is usable. `values.yaml` should define the same URL default that the template and README expect, e.g. `http://localhost:5005`.</violation>
</file>
<file name="helm/ARCHITECTURE.md">
<violation number="1" location="helm/ARCHITECTURE.md:1">
P3: The Application config (ConfigMap) reference table is missing 6 LLM provider base URL values that exist in `values.yaml` and are rendered into the ConfigMap template: `config.openaiBaseUrl` → `OPENAI_BASE_URL`, `config.anthropicBaseUrl` → `ANTHROPIC_BASE_URL`, `config.geminiBaseUrl` → `GEMINI_BASE_URL`, `config.mistralBaseUrl` → `MISTRAL_BASE_URL`, `config.openrouterBaseUrl` → `OPENROUTER_BASE_URL`, `config.ollamaBaseUrl` → `OLLAMA_BASE_URL`. Also missing `config.gitSync.image`. Add them to keep the reference complete.</violation>
<violation number="2" location="helm/ARCHITECTURE.md:227">
P3: The Secret vs ConfigMap split section lists `NAO_LICENSE` as a key rendered from secrets, but no `NAO_LICENSE` key exists in the secret template (`helm/templates/secret.yaml`) or in `helm/values.yaml`. This looks like a leftover from a now-removed feature. Remove it from the doc to avoid confusion.</violation>
</file>
<file name="helm/templates/_helpers.tpl">
<violation number="1" location="helm/templates/_helpers.tpl:96">
P1: The `DB_URI` secret will contain a broken connection string when `postgresql.enabled=true` and `postgresql.auth.password` is not explicitly set. The default password is an empty string in `values.yaml`, and at Helm template-render time `.Values.postgresql.auth.password` is still `""` — so the rendered `DB_URI` becomes `postgres://nao:@nao-postgresql:5432/nao` with no password. When the application pod starts, it will fail to authenticate against PostgreSQL because the Bitnami subchart generates a random password that is never reflected in this URI.
**Recommendation**: Add a `required` call to fail early with a clear message when the password is empty, instead of silently producing a broken URI at runtime:
```yaml
{{- define "nao.dbUri" -}}
{{- if .Values.postgresql.enabled -}}
{{- $password := required "postgresql.auth.password must be set when postgresql.enabled=true" .Values.postgresql.auth.password -}}
{{- printf "postgres://%s:%s@%s:5432/%s"
.Values.postgresql.auth.username
$password
(include "nao.postgresqlHost" .)
.Values.postgresql.auth.database -}}
{{- else -}}
{{- .Values.secrets.dbUri -}}
{{- end -}}
{{- end }}
Alternatively, update the values.yaml default comment to indicate the password is required (not just "Change in production") and document both fixes.
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| 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" |
There was a problem hiding this comment.
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>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Signed-off-by: Maxime Fournioux <55544262+mfournioux@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Signed-off-by: Maxime Fournioux <55544262+mfournioux@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Signed-off-by: Maxime Fournioux <55544262+mfournioux@users.noreply.github.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="helm/README.md">
<violation number="1" location="helm/README.md:117">
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.</violation>
</file>
<file name="helm/Chart.yaml">
<violation number="1" location="helm/Chart.yaml:6">
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.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
The Deployment uses envFrom with configMapRef and secretRef but does not change when only the referenced ConfigMap or Secret data changes, causing helm upgrade to silently skip rolling out updated env values. Adding checksum annotations for both templates triggers an automatic rollout when configuration or secret data changes.
When contextSource=api, the chart set NAO_CONTEXT_SOURCE=api but did not configure NAO_PROJECTS_DIR, which the backend uses to locate dynamically deployed projects in /app/projects. This caused context endpoints to fail in api mode. Adding NAO_PROJECTS_DIR=/app/projects when contextSource=api ensures the backend can find deployed projects on the PVC
The bitnamilegacy/postgresql image requires allowInsecureImages: true to pass Bitnami's container image verification. This is a temporary setting during the migration from the Bitnami postgresql subchart. Document it clearly in values.yaml so users understand it's a short-term workaround and know how to remove it when the chart switches to the official postgres image.
…config table Add 7 missing values to the Application config (ConfigMap) reference table: config.gitSync.image, config.openaiBaseUrl, config.anthropicBaseUrl, config.geminiBaseUrl, config.mistralBaseUrl, config.openrouterBaseUrl, config.ollamaBaseUrl. These are rendered into the ConfigMap but were previously undocumented.
NAO_LICENSE was previously removed from templates/secret.yaml and values.yaml but the Secret vs ConfigMap split section in ARCHITECTURE.md still references it. Remove it to keep docs consistent.
There was a problem hiding this comment.
All reported issues were addressed across 8 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Signed-off-by: Maxime Fournioux <55544262+mfournioux@users.noreply.github.com>
Helm Sprig's merge keeps the destination dictionary's keys — the source only fills in missing keys. The original code used merge serviceAnnotations, which made commonAnnotations silently override resource-specific annotations. Changed to merge serviceAnnotations so that service/serviceAccount-specific annotations take precedence over commonAnnotations, allowing users to override defaults on individual resources.
…port The required guard on postgresql.auth.password breaks valid Bitnami workflows like auth.existingSecret or global password override. Replace with a graceful fallback: when existingSecret is set, use secrets.dbUri from values.yaml instead. Document that existingSecret requires manual dbUri configuration. This aligns with Bitnami's secret management patterns while keeping the inline password path working as before.
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Signed-off-by: Maxime Fournioux <55544262+mfournioux@users.noreply.github.com>
|
Hello @Bl3f, regarding this PR, do you need additional tests to validate the helm chart ? |
|
Hey @mfournioux, sorry I was away with limited internet connection the last 2 weeks so it was hard to test the Helm charts. I'll try to work on it before end of week |
Summary
This PR adds a production-ready Helm chart for deploying nao— an open-source analytics agent that transforms natural language into SQL queries — on Kubernetes. This is the template to deploy nao as a managed service on K8s clusters.
What's included
Helm chart structure
helm/
├── Chart.yaml # chart metadata (name, version, dependencies)
├── Chart.lock # locked subchart versions
├── values.yaml # default + customizable configuration
├── README.md # installation guide with examples
├── ARCHITECTURE.md # runtime architecture & resource reference
├── charts/ # subcharts (bitnami/postgresql)
├── templates/ # 10 Kubernetes resource templates
│ ├── _helpers.tpl # reusable template helpers
│ ├── deployment.yaml # main nao workload (container + optional initContainer)
│ ├── service.yaml # ClusterIP service for internal traffic
│ ├── pvc.yaml # persistent volume for local context storage
│ ├── configmap.yaml # nao configuration (contextPath, env defaults)
│ ├── secret.yaml # sensitive values (API keys, auth secret, DB URI)
│ ├── serviceaccount.yaml # service account for the pod
│ ├── hpa.yaml # Horizontal Pod Autoscaler (optional)
│ ├── pdb.yaml # Pod Disruption Budget (optional)
│ ├── NOTES.txt # post-install messages
│ └── tests/
│ └── test-connection.yaml # helm test pod
└── .helmignore
Architecture overview
Runtime topology
Nao container components
The nao image runs a supervisord process that manages:
On startup, supervisord runs as root then drops to uid 1000 (
naouser) for security.Three context modes
Nao supports three ways to load project context (database schemas, metadata, docs):
localgit-syncinitContainer clones from GitgitNAO_CONTEXT_GIT_URLon startup (no initContainer needed)apinao deployCLI — no context volume at allThe
git-syncinitContainer is only created when all three conditions are met:contextSourceislocalpersistence.enabledistruegitSync.urlis provided (non-empty)It uses the official
registry.k8s.io/git-syncimage and injects a read token directly into the HTTPS URL (no SSH, no custom CA certs).Environment variables
The deployment sets the following key environment variables:
DATABASE_URLpostgresql://<user>:<pass>@<host>:5432/<db>NAO_DEFAULT_PROJECT_PATH/app/projectNAO_CONTEXT_SOURCElocalNAO_CONTEXT_GIT_URLgitmodeBETTER_AUTH_URLOPENAI_API_KEYBETTER_AUTH_SECRETDeployment procedure
Prerequisites
helmkubectlconfigured1. Generate secrets
Generate a Better Auth secret (signs sessions) — generate per deployment:
Save the output — you'll pass it to
helm install. You'll also need:2. Basic install (PostgreSQL auto-provisioned)
This deploys everything: nao pod, PostgreSQL (Bitnami), service.
3. Custom install with git-sync
If you want to sync project context from Git on startup:
The initContainer clones the context repo into the PVC before nao starts.
4. Using your own PostgreSQL
5. Verify deployment
Values reference
See
helm/values.yamlfor the complete list of configurable values. Key sections:imagereplicaCountconfigpostgresqlpersistencegitSyncsecretsserviceresourcesautoscalingpodDisruptionBudgetnodeSelector,tolerations,affinityDesign decisions
""with inline comments instructing users to generate them per deploymentIfNotPresent— useimagePullSecretsfor private registriesTesting
The chart has been validated with: