feat(helm): add Helm chart for Kubernetes deployment#1158
Conversation
Add a Helm chart under charts/vane to deploy Vane on Kubernetes. A single `variant` value selects the official image: - full (default): itzcrazykns1337/vane:latest, with SearXNG bundled in the image - slim: itzcrazykns1337/vane:slim-latest, with SearXNG either deployed by the chart (searxng.deploy=true) or referenced externally (searxng.url) Highlights: - Persistent volumes for the config/SQLite database and uploaded files - Optional in-cluster SearXNG (Deployment/Service/Secret) configured with JSON format and the Wolfram Alpha engine, with a stable auto-generated secret key - Ingress, HPA, liveness/readiness/startup probes and scheduling controls - values.schema.json for install-time validation of the values - ServiceAccount API token auto-mounting disabled by default - Guardrails that fail early on an invalid variant or a slim install with no search backend Also document the chart in the README installation section and exclude the chart templates from Prettier (Go templating conflicts with it).
There was a problem hiding this comment.
7 issues found across 19 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="charts/vane/README.md">
<violation number="1" location="charts/vane/README.md:26">
P2: The README hardcodes `vane` as the Helm release name across all example commands without any note or placeholder to indicate it should be substituted. Since the service name is rendered via `vane.fullname` and the PVC label selector targets `app.kubernetes.io/instance={{ .Release.Name }}`, any user who installs with a different release name will have broken follow-up commands (`kubectl port-forward svc/vane` will target the wrong service, `kubectl delete pvc -l app.kubernetes.io/instance=vane` will silently miss resources). Consider adding a note such as "Replace `vane` with your chosen Helm release name in the commands below" or switching to a `<release-name>` placeholder so the documentation stays correct regardless of the install name.</violation>
</file>
<file name="charts/vane/values.schema.json">
<violation number="1" location="charts/vane/values.schema.json:76">
P2: Allowing `ExternalName` in the service type enum without an accompanying `externalName` property creates a broken contract: Kubernetes requires `spec.externalName` for `ExternalName` Services, but the schema blocks that field (`additionalProperties: false`) and the templates never reference it. Users who select `ExternalName` will either fail schema validation or produce an invalid Kubernetes manifest. Consider removing `ExternalName` from the enum, or adding an `externalName` string property to the schema and referencing it in the Service templates.</violation>
</file>
<file name="charts/vane/templates/_helpers.tpl">
<violation number="1" location="charts/vane/templates/_helpers.tpl:147">
P1: The `vane.searxng.secretKey` helper falls back to `randAlphaNum 64` when `lookup` finds no existing secret. Because `lookup` returns empty during `helm template` (used by ArgoCD, Flux, and many CI diff steps), every render generates a different secret. That produces non-deterministic manifests and perpetual GitOps drift. On every sync the generated Secret changes, which also rolls the SearXNG pod because the Deployment includes a `checksum/secret` annotation keyed off the rendered secret manifest. Consider adding clear documentation (and ideally a schema guard) that `searxng.secretKey` must be explicitly set for deterministic/GitOps deployments, or replace the non-deterministic fallback with a stable derivation when no cluster state is available.</violation>
</file>
<file name="charts/vane/values.yaml">
<violation number="1" location="charts/vane/values.yaml:18">
P2: Default image tags use floating references (`latest` / `slim-latest`), which breaks Helm's reproducibility guarantees and makes rollbacks unreliable. Because `pullPolicy` is `IfNotPresent`, nodes that already cached an older `latest` image will continue running it while new nodes pull the current one, creating cluster-level version skew. Consider defaulting the Vane image to the chart's declared `appVersion` (`1.12.2`) and pinning SearXNG to a specific release tag instead of `latest`.</violation>
</file>
<file name="charts/vane/templates/hpa.yaml">
<violation number="1" location="charts/vane/templates/hpa.yaml:14">
P2: The HPA allows scaling to `maxReplicas` (default 3), but the chart defaults to a ReadWriteOnce PVC for a local SQLite database that does not support multi-writer access. When autoscaling triggers additional replicas, those pods will fail to schedule because the RWO volume cannot be mounted concurrently, leading to multi-attach volume errors and a broken deployment. Consider adding a chart guard or schema validation that prevents enabling autoscaling with a `maxReplicas > 1` while the default RWO persistence is in use, or at least automatically cap `maxReplicas` to 1 when using a single-writer volume.</violation>
</file>
<file name="charts/vane/templates/deployment.yaml">
<violation number="1" location="charts/vane/templates/deployment.yaml:21">
P2: When `.Values.podAnnotations` is unset, the template renders `metadata.annotations` as `null` instead of omitting the key. Strict schema validators and policy engines may reject `null` for object-typed fields like `annotations`. It is safer to wrap the entire `annotations:` block in the `with` so the key is only emitted when annotations actually exist.</violation>
<violation number="2" location="charts/vane/templates/deployment.yaml:90">
P2: `volumeMounts:` and `volumes:` are always emitted, but their list items are conditional. When a user disables both PVCs (set `persistence.data.enabled=false` and `persistence.uploads.enabled=false`) and provides no extras, both fields render as null arrays. Some strict Kubernetes validators or admission policies reject null list values, which can break CI or deployment pipelines. The adjacent `env:` block already avoids this by wrapping the whole field in a conditional; applying the same guard to `volumeMounts` and `volumes` would make the chart more robust.
Wrap each list field in an `or` check (e.g., `{{- if or .Values.persistence.data.enabled .Values.persistence.uploads.enabled .Values.extraVolumeMounts }}`) so the header is only emitted when at least one item will follow.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| {{- if and $existing $existing.data (index $existing.data "secretKey") -}} | ||
| {{- index $existing.data "secretKey" | b64dec -}} | ||
| {{- else -}} | ||
| {{- randAlphaNum 64 -}} |
There was a problem hiding this comment.
P1: The vane.searxng.secretKey helper falls back to randAlphaNum 64 when lookup finds no existing secret. Because lookup returns empty during helm template (used by ArgoCD, Flux, and many CI diff steps), every render generates a different secret. That produces non-deterministic manifests and perpetual GitOps drift. On every sync the generated Secret changes, which also rolls the SearXNG pod because the Deployment includes a checksum/secret annotation keyed off the rendered secret manifest. Consider adding clear documentation (and ideally a schema guard) that searxng.secretKey must be explicitly set for deterministic/GitOps deployments, or replace the non-deterministic fallback with a stable derivation when no cluster state is available.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At charts/vane/templates/_helpers.tpl, line 147:
<comment>The `vane.searxng.secretKey` helper falls back to `randAlphaNum 64` when `lookup` finds no existing secret. Because `lookup` returns empty during `helm template` (used by ArgoCD, Flux, and many CI diff steps), every render generates a different secret. That produces non-deterministic manifests and perpetual GitOps drift. On every sync the generated Secret changes, which also rolls the SearXNG pod because the Deployment includes a `checksum/secret` annotation keyed off the rendered secret manifest. Consider adding clear documentation (and ideally a schema guard) that `searxng.secretKey` must be explicitly set for deterministic/GitOps deployments, or replace the non-deterministic fallback with a stable derivation when no cluster state is available.</comment>
<file context>
@@ -0,0 +1,164 @@
+{{- if and $existing $existing.data (index $existing.data "secretKey") -}}
+{{- index $existing.data "secretKey" | b64dec -}}
+{{- else -}}
+{{- randAlphaNum 64 -}}
+{{- end -}}
+{{- end -}}
</file context>
|
|
||
| ```bash | ||
| # Full image (Vane + bundled SearXNG) — the recommended default | ||
| helm install vane ./charts/vane |
There was a problem hiding this comment.
P2: The README hardcodes vane as the Helm release name across all example commands without any note or placeholder to indicate it should be substituted. Since the service name is rendered via vane.fullname and the PVC label selector targets app.kubernetes.io/instance={{ .Release.Name }}, any user who installs with a different release name will have broken follow-up commands (kubectl port-forward svc/vane will target the wrong service, kubectl delete pvc -l app.kubernetes.io/instance=vane will silently miss resources). Consider adding a note such as "Replace vane with your chosen Helm release name in the commands below" or switching to a <release-name> placeholder so the documentation stays correct regardless of the install name.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At charts/vane/README.md, line 26:
<comment>The README hardcodes `vane` as the Helm release name across all example commands without any note or placeholder to indicate it should be substituted. Since the service name is rendered via `vane.fullname` and the PVC label selector targets `app.kubernetes.io/instance={{ .Release.Name }}`, any user who installs with a different release name will have broken follow-up commands (`kubectl port-forward svc/vane` will target the wrong service, `kubectl delete pvc -l app.kubernetes.io/instance=vane` will silently miss resources). Consider adding a note such as "Replace `vane` with your chosen Helm release name in the commands below" or switching to a `<release-name>` placeholder so the documentation stays correct regardless of the install name.</comment>
<file context>
@@ -0,0 +1,129 @@
+
+```bash
+# Full image (Vane + bundled SearXNG) — the recommended default
+helm install vane ./charts/vane
+
+# Slim image, letting the chart deploy SearXNG for you
</file context>
| "properties": { | ||
| "type": { | ||
| "type": "string", | ||
| "enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"] |
There was a problem hiding this comment.
P2: Allowing ExternalName in the service type enum without an accompanying externalName property creates a broken contract: Kubernetes requires spec.externalName for ExternalName Services, but the schema blocks that field (additionalProperties: false) and the templates never reference it. Users who select ExternalName will either fail schema validation or produce an invalid Kubernetes manifest. Consider removing ExternalName from the enum, or adding an externalName string property to the schema and referencing it in the Service templates.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At charts/vane/values.schema.json, line 76:
<comment>Allowing `ExternalName` in the service type enum without an accompanying `externalName` property creates a broken contract: Kubernetes requires `spec.externalName` for `ExternalName` Services, but the schema blocks that field (`additionalProperties: false`) and the templates never reference it. Users who select `ExternalName` will either fail schema validation or produce an invalid Kubernetes manifest. Consider removing `ExternalName` from the enum, or adding an `externalName` string property to the schema and referencing it in the Service templates.</comment>
<file context>
@@ -0,0 +1,211 @@
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"]
+ },
+ "port": { "type": "integer", "minimum": 1, "maximum": 65535 }
</file context>
| repository: itzcrazykns1337/vane | ||
| # -- Overrides the image tag. When empty the tag is derived from `variant`: | ||
| # "full" -> "latest", "slim" -> "slim-latest". | ||
| tag: '' |
There was a problem hiding this comment.
P2: Default image tags use floating references (latest / slim-latest), which breaks Helm's reproducibility guarantees and makes rollbacks unreliable. Because pullPolicy is IfNotPresent, nodes that already cached an older latest image will continue running it while new nodes pull the current one, creating cluster-level version skew. Consider defaulting the Vane image to the chart's declared appVersion (1.12.2) and pinning SearXNG to a specific release tag instead of latest.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At charts/vane/values.yaml, line 18:
<comment>Default image tags use floating references (`latest` / `slim-latest`), which breaks Helm's reproducibility guarantees and makes rollbacks unreliable. Because `pullPolicy` is `IfNotPresent`, nodes that already cached an older `latest` image will continue running it while new nodes pull the current one, creating cluster-level version skew. Consider defaulting the Vane image to the chart's declared `appVersion` (`1.12.2`) and pinning SearXNG to a specific release tag instead of `latest`.</comment>
<file context>
@@ -0,0 +1,200 @@
+ repository: itzcrazykns1337/vane
+ # -- Overrides the image tag. When empty the tag is derived from `variant`:
+ # "full" -> "latest", "slim" -> "slim-latest".
+ tag: ''
+ pullPolicy: IfNotPresent
+
</file context>
| kind: Deployment | ||
| name: {{ include "vane.fullname" . }} | ||
| minReplicas: {{ .Values.autoscaling.minReplicas }} | ||
| maxReplicas: {{ .Values.autoscaling.maxReplicas }} |
There was a problem hiding this comment.
P2: The HPA allows scaling to maxReplicas (default 3), but the chart defaults to a ReadWriteOnce PVC for a local SQLite database that does not support multi-writer access. When autoscaling triggers additional replicas, those pods will fail to schedule because the RWO volume cannot be mounted concurrently, leading to multi-attach volume errors and a broken deployment. Consider adding a chart guard or schema validation that prevents enabling autoscaling with a maxReplicas > 1 while the default RWO persistence is in use, or at least automatically cap maxReplicas to 1 when using a single-writer volume.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At charts/vane/templates/hpa.yaml, line 14:
<comment>The HPA allows scaling to `maxReplicas` (default 3), but the chart defaults to a ReadWriteOnce PVC for a local SQLite database that does not support multi-writer access. When autoscaling triggers additional replicas, those pods will fail to schedule because the RWO volume cannot be mounted concurrently, leading to multi-attach volume errors and a broken deployment. Consider adding a chart guard or schema validation that prevents enabling autoscaling with a `maxReplicas > 1` while the default RWO persistence is in use, or at least automatically cap `maxReplicas` to 1 when using a single-writer volume.</comment>
<file context>
@@ -0,0 +1,32 @@
+ kind: Deployment
+ name: {{ include "vane.fullname" . }}
+ minReplicas: {{ .Values.autoscaling.minReplicas }}
+ maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+ metrics:
+ {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
</file context>
| resources: | ||
| {{- toYaml . | nindent 12 }} | ||
| {{- end }} | ||
| volumeMounts: |
There was a problem hiding this comment.
P2: volumeMounts: and volumes: are always emitted, but their list items are conditional. When a user disables both PVCs (set persistence.data.enabled=false and persistence.uploads.enabled=false) and provides no extras, both fields render as null arrays. Some strict Kubernetes validators or admission policies reject null list values, which can break CI or deployment pipelines. The adjacent env: block already avoids this by wrapping the whole field in a conditional; applying the same guard to volumeMounts and volumes would make the chart more robust.
Wrap each list field in an or check (e.g., {{- if or .Values.persistence.data.enabled .Values.persistence.uploads.enabled .Values.extraVolumeMounts }}) so the header is only emitted when at least one item will follow.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At charts/vane/templates/deployment.yaml, line 90:
<comment>`volumeMounts:` and `volumes:` are always emitted, but their list items are conditional. When a user disables both PVCs (set `persistence.data.enabled=false` and `persistence.uploads.enabled=false`) and provides no extras, both fields render as null arrays. Some strict Kubernetes validators or admission policies reject null list values, which can break CI or deployment pipelines. The adjacent `env:` block already avoids this by wrapping the whole field in a conditional; applying the same guard to `volumeMounts` and `volumes` would make the chart more robust.
Wrap each list field in an `or` check (e.g., `{{- if or .Values.persistence.data.enabled .Values.persistence.uploads.enabled .Values.extraVolumeMounts }}`) so the header is only emitted when at least one item will follow.</comment>
<file context>
@@ -0,0 +1,127 @@
+ resources:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
+ volumeMounts:
+ {{- if .Values.persistence.data.enabled }}
+ - name: data
</file context>
| annotations: | ||
| {{- with .Values.podAnnotations }} | ||
| {{- toYaml . | nindent 8 }} |
There was a problem hiding this comment.
P2: When .Values.podAnnotations is unset, the template renders metadata.annotations as null instead of omitting the key. Strict schema validators and policy engines may reject null for object-typed fields like annotations. It is safer to wrap the entire annotations: block in the with so the key is only emitted when annotations actually exist.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At charts/vane/templates/deployment.yaml, line 21:
<comment>When `.Values.podAnnotations` is unset, the template renders `metadata.annotations` as `null` instead of omitting the key. Strict schema validators and policy engines may reject `null` for object-typed fields like `annotations`. It is safer to wrap the entire `annotations:` block in the `with` so the key is only emitted when annotations actually exist.</comment>
<file context>
@@ -0,0 +1,127 @@
+ {{- include "vane.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ annotations:
+ {{- with .Values.podAnnotations }}
+ {{- toYaml . | nindent 8 }}
</file context>
| annotations: | |
| {{- with .Values.podAnnotations }} | |
| {{- toYaml . | nindent 8 }} | |
| {{- with .Values.podAnnotations }} | |
| annotations: | |
| {{- toYaml . | nindent 8 }} | |
| {{- end }} |
Summary
Adds a Helm chart under
charts/vaneto deploy Vane on Kubernetes. A singlevariantvalue selects the official image, so both published images are covered by one chart:variantfull(default)itzcrazykns1337/vane:latestslimitzcrazykns1337/vane:slim-latestsearxng.deploy=true) or external (searxng.url)What's included
/home/vane/data) and uploads (/home/vane/uploads)searxng/settings.yml(JSON format + Wolfram Alpha engine enabled), with a stable auto-generatedsecret_key, wired to Vane throughSEARXNG_API_URLvalues.schema.jsonfor install-time validation of the valuesautomountServiceAccountToken: falsefail) for an invalidvariantor a slim install with no search backendUsage
See
charts/vane/README.mdfor the full list of options.Notes / tradeoffs
latest/slim-latesttags; overrideimage.tagto pin a release such as1.12.2/slim-1.12.2.securityContextby default: thefullimage starts SearXNG viasudoand both images run as root, so hardening is left configurable rather than forced.ReadWriteOncevolume, so the chart defaults toreplicaCount: 1with aRecreatestrategy.Validation
helm lintpasses forfull,slim+ in-cluster SearXNG, andslim+ external SearXNGhelm templaterenders valid manifests in all three modes; the JSON schema rejects invalid values (unknown keys, wrong types, out-of-range ports, invalid variant)prettierclean — chart templates are excluded from Prettier via.prettierignorebecause Go templating conflicts with itSummary by cubic
Add a Helm chart to deploy Vane on Kubernetes, supporting both
fullandslimimages via a singlevariantswitch. This makes K8s installs simple, with optional in-cluster SearXNG, persistence, and basic ops features.charts/vanewithvariant=full|slim; slim supports in-cluster or external SearXNG.automountServiceAccountToken=false./home/vane/dataand/home/vane/uploads.searxng/searxng(JSON format + Wolfram Alpha enabled) with a stable auto-generatedsecret_key, or usesearxng.url.values.schema.jsonand guardrails for invalidvariantor slim without a search backend..prettierignoreexcludes Helm templates.Written for commit b39f0a0. Summary will update on new commits.