{{- $replicas := include "searxng.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.searxng.replicas) -}} {{- $settings := fromYaml (include "searxng.effectiveSettings" .Values.settings) -}} {{- $httpRoute := .Values.httpRoute | default dict -}} {{- $hostnames := $httpRoute.hostnames | default list -}} {{- if not $hostnames -}} {{- $hostnames = list .Values.ingress.domain -}} {{- end -}} SearXNG {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}. {{- if .Values.ingress.enabled }} ACCESS — INGRESS MODE (ingress.enabled: true, httpRoute.enabled: false) https://{{ .Values.ingress.domain }} The certificate is requested by cert-manager through the "{{ .Values.ingress.clusterIssuer }}" cluster issuer. Until it has been issued the browser shows a warning; follow it with kubectl -n {{ .Release.Namespace }} get certificate tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress {{- else if $httpRoute.enabled }} ACCESS — GATEWAY MODE (httpRoute.enabled: true, ingress.enabled: false) {{- range $hostnames }} https://{{ . }} {{- end }} This release renders an HTTPRoute ({{ .Release.Name }}-{{ .Chart.Name }}-httproute) and NOTHING else of the routing path — no Gateway, no class, no Certificate. Three things therefore belong to the cluster and not to this chart: 1. The Gateway-API CRDs. Without them this install would have failed. 2. The Gateway named in httpRoute.parentRefs {{- range $httpRoute.parentRefs }} {{ .name }}{{ if .namespace }} in namespace {{ .namespace }}{{ end }}{{ if .sectionName }}, listener {{ .sectionName }}{{ end }} {{- end }} must exist AND its listener must allow routes from this namespace ({{ .Release.Namespace }}) in allowedRoutes. A rejected attachment shows up only in the route's status: kubectl -n {{ .Release.Namespace }} get httproute {{ .Release.Name }}-{{ .Chart.Name }}-httproute -o yaml Read status.parents[].conditions — "Accepted: True" and "ResolvedRefs: True" is a working attachment. An empty status means no controller has looked at the route at all. 3. The TLS certificate for the hostname, on the Gateway's listener. ingress.clusterIssuer is NOT used in this mode and is not required: the chart renders no Certificate, so there is no tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress secret and nothing for cert-manager to do. SEARXNG_BASE_URL still follows ingress.domain ("https://{{ .Values.ingress.domain }}/"), not httpRoute.hostnames: signed image-proxy URLs and the OpenSearch description keep naming that host whatever the route publishes. AND THE THING THIS MODE TAKES AWAY: the forward-auth escape hatch. The Gateway API has no annotation channel, so the Authentik-style auth annotations documented under ingress.annotations in values.yaml have NO counterpart here, and the chart renders no implementation-specific policy object on purpose. SearXNG has no login of its own, so "who may use this instance" is entirely the Gateway's question in this mode — a listener that is not publicly reachable, or that implementation's own auth filter, configured next to the Gateway and not here. {{- else }} ACCESS — NEITHER INGRESS NOR HTTPROUTE ingress.enabled and httpRoute.enabled are both false, so this release has no Ingress, no HTTPRoute and no certificate. ingress.domain is still required: it builds SEARXNG_BASE_URL, which SearXNG puts into its OpenSearch description, into the signed image-proxy URLs and into every absolute link. Reach the instance inside the cluster with kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-{{ .Chart.Name }}-service 8080:80 and open http://localhost:8080. Note that links on the page will still point at https://{{ .Values.ingress.domain }}/. {{- end }} {{ if eq $replicas "0" }} SCALED TO ZERO {{ if .Values.scaleDown }}scaleDown: true{{ else }}searxng.replicas: 0{{ end }} — the deployment runs with replicas: 0, so the endpoint above does not answer until you set it back and upgrade again. Service{{ if .Values.ingress.enabled }} and Ingress stay in place, so the certificate is not re-issued.{{ else if $httpRoute.enabled }} and HTTPRoute stay in place, so the route keeps pointing at a Service with no endpoints — the Gateway answers with an error of its own rather than with nothing.{{ else }} stays in place.{{ end }} {{ end }} CONFIGURATION Instance name: {{ $settings.instanceName }} settings.yml is rendered from the "settings" values into {{ .Release.Name }}-{{ .Chart.Name }}-configmap and mounted at /etc/searxng/settings.yml. It starts with use_default_settings: true and names only the deviations, so every engine the image ships stays available and upstream engine changes arrive with the app version instead of with a chart release. Anything the chart does not model first-class goes into settings.extra in SearXNG's own key names; it is merged over the generated block. Changing any of it rolls the pods — the deployment hashes the ConfigMap, because a subPath mount would otherwise never reach the running container. WHAT THE GREEN CHECKMARK DOES AND DOES NOT MEAN All three probes hit /healthz, the only path SearXNG's bot detection lets through unconditionally. Its handler returns a fixed "OK". So a Ready pod means the worker answers — not that a search works, not that any engine is reachable, and not that a login placed in front of it is up (probes go straight to the pod and never pass the {{ if $httpRoute.enabled }}Gateway{{ else }}Ingress{{ end }}). {{- if not $settings.limiter }} BOT DETECTION IS OFF {{- if $httpRoute.enabled }} settings.limiter is false and no Valkey is configured. The instance relies on whatever sits in front of it — and in GATEWAY MODE that cannot be configured from this chart: ingress.annotations has no counterpart, so a login has to be put on the Gateway itself (a listener that is not publicly reachable, or that implementation's own auth filter). The limiter itself is unaffected by the mode; it runs inside SearXNG, not at the proxy. {{- else }} settings.limiter is false and no Valkey is configured. The instance relies on whatever sits in front of it — put authentication there via ingress.annotations if it must not be open to everyone. Mind that this escape hatch exists in Ingress mode only: httpRoute.enabled has no annotation channel at all. {{- end }} {{- else if not .Values.secrets.valkeySecretRef }} BOT DETECTION IS ON WITHOUT A VALKEY settings.limiter is true but secrets.valkeySecretRef is unset. The limiter needs a Valkey to keep its counters, so it does nothing at all: SearXNG logs ERROR:searx.limiter: The limiter requires Valkey, please consult the documentation: https://docs.searxng.org/admin/searx.limiter.html once at worker start, the pod becomes Ready, and every request is served unlimited. This release is therefore NOT rate limited, and the only trace of that is one line at the top of the log. Set secrets.valkeySecretRef, or set settings.limiter back to false. {{- end }} ROTATING THE SIGNING KEY secrets.searxngSecretRef carries the key that signs the settings cookie and the image-proxy URLs. Pointing it at a DIFFERENT 1Password item rolls the pods; changing the value INSIDE the same item does not — Helm never sees that content. After such a rotation: kubectl -n {{ .Release.Namespace }} rollout restart deployment/{{ .Release.Name }}-{{ .Chart.Name }}-deployment A new key invalidates every user's saved preferences cookie, which is a logout-shaped event for the people using it. NOTHING TO BACK UP The pod holds no state worth keeping: settings.yml comes from the ConfigMap, the signing key from the Secret, and the SQLite caches under /tmp plus /var/cache/searxng are emptyDirs that refill on their own. App-version upgrades and rollbacks therefore carry no data risk.