Table of Contents
- searxng
- Prerequisites
- Installation
- Configurable values
- Operating notes
- Routing: Ingress or Gateway API
- ingress.domain is read twice
- settings.extra is the real configuration surface
- Why all three probes use /healthz
- The limiter fails quietly without a Valkey
- Authentication belongs on the Ingress
- GRANIAN_HOST and the SEARXNG_* precedence trap
- Rotating the signing key does not restart the pod
- The expected ownership warning
- 443 goes anywhere, and 6379 is the standard port rather than yours
- The probed port is open inside the cluster by default
- The policy is only a boundary where the CNI enforces one
- Upgrades and backup
- Renovate
- CI
- See also
searxng
Deploys SearXNG, a metasearch engine, as a stateless Deployment. The whole settings.yml is rendered from values into a ConfigMap; the signing key comes from 1Password. The chart ships no Valkey/Redis of its own.
| Image | docker.io/searxng/searxng |
| Versions | not listed on purpose — read the current pair off searxng/Chart.yaml, see Chart index |
| Workloads | Deployment (1 replica) |
| Storage | none — two emptyDirs (/tmp, /var/cache/searxng) |
| Routing | two mutually exclusive modes, Ingress (default) or Gateway API (httpRoute.enabled). ingress.domain always required, ingress.clusterIssuer only while the Ingress is on — see Routing: Ingress or Gateway API |
| External dependencies | none by default; an external Valkey/Redis only for the limiter |
| Secrets | 1 × OnePasswordItem, a second one when a Valkey is configured |
| Install-test | ci/searxng/ (fixtures + test values) |
Prerequisites
- 1Password operator in the cluster, plus one 1Password item — see the fields below. Without it the pod never starts: SearXNG exits 1 rather than run on the upstream placeholder key.
- cert-manager with the
ClusterIssuernamed iningress.clusterIssuer, and DNS foringress.domainpointing at the ingress controller. - Something that authenticates in front of it, if it must not be open. SearXNG has no login of its own and this chart adds none; the intended place is
ingress.annotations(forward-auth), see Authentication belongs on the Ingress. - An external Valkey/Redis only if
settings.limiteris switched on. Nothing else in the chart needs one.
Installation
The registry URL and its credentials live in 1Password (item jlab-registry-access: username, password, charts-url) and never in a repository or a shell history file.
helm repo add jlab "$CHARTS_URL" --username "$REG_USER" --password "$REG_PASS"
helm repo update
helm install searxng jlab/searxng \
--namespace searxng --create-namespace \
-f my-values.yaml
Minimal my-values.yaml:
secrets:
searxngSecretRef: op://vault/searxng
ingress:
domain: search.example.com
clusterIssuer: letsencrypt
The same installation in Gateway mode instead — the Gateway, its class and its certificate belong to the cluster, not to this release:
ingress:
enabled: false
domain: <the public hostname> # the HTTPRoute's hostname, see below
httpRoute:
enabled: true
parentRefs:
- name: public
namespace: gateway
sectionName: https
This needs the Gateway-API CRDs installed and a Gateway whose listener admits routes from this namespace; ingress.clusterIssuer is not used. ingress.domain is still required — it is also SEARXNG_BASE_URL. See Routing: Ingress or Gateway API.
helm install prints the chart's NOTES.txt, which renders the same information against the values actually set — including whether this release is rate limited at all. Read it — it is the per-release version of this page.
What the 1Password items must contain
The chart renders OnePasswordItem CRs; the operator turns each into a Kubernetes Secret whose keys are the field names of the 1Password item. A missing field does not fail the install: the release goes through and the pod then hangs in CreateContainerConfigError naming the key it could not find.
| Values key | Kubernetes Secret | Fields |
|---|---|---|
secrets.searxngSecretRef |
<release>-searxng-secret |
secret-key — signs the settings cookie and the image-proxy URLs. Anything but the upstream placeholder ultrasecretkey; openssl rand -hex 32 |
secrets.valkeySecretRef |
<release>-searxng-valkey-secret |
valkey-url — e.g. redis://valkey.valkey.svc:6379/0. Only rendered when the value is set |
The key is a Secret rather than a ConfigMap key on purpose: a ConfigMap is what people read without thinking twice, and this value signs every user's cookie.
Configurable values
Global
| Key | Type | Default | Description |
|---|---|---|---|
scaleDown |
bool | false |
Renders every workload with replicas: 0 without uninstalling. Takes precedence over searxng.replicas. Secret, Service, ConfigMap and Ingress stay. |
global |
object | unset | Helm's conventional cross-chart values. Unused by this chart; declared in the schema and kept open so an umbrella install cannot break on it. |
secrets
| Key | Type | Default | Description |
|---|---|---|---|
secrets.searxngSecretRef |
string | null |
Required, always. op:// path of the item carrying secret-key. |
secrets.valkeySecretRef |
string | null |
op:// path of an external Valkey/Redis item. Optional; renders a second OnePasswordItem and passes SEARXNG_VALKEY_URL. Only meaningful with settings.limiter: true. |
settings
The chart-owned part of /etc/searxng/settings.yml. The file starts with use_default_settings: true and names only the deviations, so the image's ~3500-line default settings stay the source of truth for engines.
| Key | Type | Default | Description |
|---|---|---|---|
settings.instanceName |
string | SearXNG |
general.instance_name — shown in the UI and in the OpenSearch description. |
settings.safeSearch |
int | 0 |
search.safe_search; 0 off, 1 moderate, 2 strict. Enumerated in the schema — SearXNG accepts nothing else. |
settings.autocomplete |
string | duckduckgo |
search.autocomplete; the empty string disables suggestions. Not enumerated — the backend list is upstream's and grows. |
settings.imageProxy |
bool | true |
server.image_proxy — thumbnails routed through the instance instead of fetched by the browser from the origin. |
settings.limiter |
bool | false |
server.limiter, the bot detection. Does nothing without secrets.valkeySecretRef — see The limiter fails quietly. |
settings.publicInstance |
bool | false |
server.public_instance. Depends on the limiter's infrastructure; off for the same reason. |
settings.extra |
object | {} |
Free-form passthrough in SearXNG's own key names, merged over the block the keys above build. Deliberately open in the schema. See settings.extra is the real configuration surface. |
settings and every block below it may be erased to null; the chart then falls back to these defaults instead of rendering an instance with an empty name.
searxng
| Key | Type | Default | Description |
|---|---|---|---|
searxng.replicas |
int | 1 |
Replica count; 0 is allowed. More than one works — settings live in a signed cookie, and all pods share the one key — but each pod warms its own /tmp cache. |
searxng.securityContext.pod |
object | non-root 977, fsGroup: 977, fsGroupChangePolicy: Always, seccompProfile: RuntimeDefault |
Merged onto the chart's pod hardening. 977 is not interchangeable: it is the image's searxng user. The seccomp profile sits here so it covers every container including init containers. |
searxng.securityContext.container |
object | readOnlyRootFilesystem: true, runAsNonRoot: true, all caps dropped, no privilege escalation |
Merged onto the chart's container hardening. The read-only root filesystem holds because /tmp and /var/cache/searxng are emptyDirs. |
searxng.livenessProbe |
object | GET /healthz:http, period 10s, timeout 5s, 3 failures |
See Why all three probes use /healthz. |
searxng.readinessProbe |
object | GET /healthz:http, period 10s, timeout 5s, 3 failures |
|
searxng.startupProbe |
object | GET /healthz:http, period 5s, 30 failures (= 150s) |
Gates liveness and readiness. A healthy start was measured at 6s in k3d, so the budget is ~25× that — margin for a cold node, not for a slow app. |
searxng.env |
list | [{name: GRANIAN_HOST, value: "::"}] |
This one is a default, not an addition to one: setting searxng.env replaces the list wholesale, so the GRANIAN_HOST entry has to be repeated. Rendered through tpl; value, secretKeyRef, configMapKeyRef forms. See GRANIAN_HOST and the SEARXNG_* precedence trap. |
searxng.resources.limitFactor |
number | 3 |
Memory and ephemeral-storage limits are computed as factor × request. |
searxng.resources.requests |
object | cpu: 0.1, memory: 256Mi, ephemeralStorage: 64Mi |
Measured: 152 MiB resident once up, 157 MiB peak across a real search; 356 KiB of SQLite cache in /tmp. |
searxng.resources.limits |
object | unset | An explicit limit always wins. A CPU limit is only rendered when set here. |
The container port (8080, named http) is not a value: a value could lie, because granian would keep listening on 8080 while the probes and Service moved. The port name is load-bearing — the Service and all three probes select by it.
ingress
| Key | Type | Default | Description |
|---|---|---|---|
ingress.enabled |
bool | true |
Gates the Ingress object only. ingress.domain stays required — see ingress.domain is read twice. Mutually exclusive with httpRoute.enabled. |
ingress.domain |
string | null |
Required, with or without the Ingress. Ingress host, and SEARXNG_BASE_URL (server.base_url). |
ingress.clusterIssuer |
string | null |
Required while the Ingress is enabled. cert-manager ClusterIssuer. Freed by ingress.enabled: false. |
ingress.className |
string | nginx |
Ingress class. |
ingress.annotations |
object | {} |
Merged with the cert-manager annotation the chart always sets. The place for forward-auth. No counterpart in Gateway mode — and for this chart that is the sharpest consequence of the switch, see Routing: Ingress or Gateway API. |
TLS is stored in tls-<release>-searxng-ingress.
httpRoute
The Gateway-API alternative to the Ingress, off by default. Added with group 1 of the Gateway-API sweep (Gitea #114); the form is the pilot's and is binding for every chart (style guide §4).
| Key | Type | Default | Description |
|---|---|---|---|
httpRoute.enabled |
bool | false |
Renders <release>-searxng-httproute instead of the Ingress. Mutually exclusive with ingress.enabled — with both true the render aborts naming both values. |
httpRoute.parentRefs |
list | [] |
The Gateway(s) the route attaches to — Gateway API ParentReference entries (name, and optionally namespace, sectionName, port, group, kind), passed through unchanged. Required while httpRoute.enabled is true: the render aborts on an empty list. The schema requires name on every entry and is otherwise left open, because Kubernetes owns that schema and adds fields to it across releases. |
httpRoute.hostnames |
list | [] |
Empty means [ingress.domain], so the hostname has a single source of truth. Set it explicitly only to publish under a different or an additional name — which is how a migration off the Ingress runs. |
What the chart renders in this mode is only the HTTPRoute: one rule, PathPrefix: /, one backendRefs entry pointing at <release>-searxng-service on port 80. SearXNG serves its UI, /search and /healthz from one worker.
networkPolicy
Inbound 8080 only. Outbound DNS, TCP 443 (the search engines — this is the application) and 6379 (the external Valkey the limiter uses).
| Value | Type | Default | Meaning |
|---|---|---|---|
networkPolicy.enabled |
bool | true |
Renders the policy. Only a boundary where the CNI enforces one — unenforced, the API server accepts it and nothing is filtered, so everything reads as configured. |
networkPolicy.allowedNamespaces |
list | [] |
Namespaces whose pods may reach the restricted ports, additionally to the release's own. Matched on the kubernetes.io/metadata.name label Kubernetes sets itself — no labelling work, and no typo protection either: a name that does not exist matches nothing and is silently ineffective. Namespace-granular, so an entry opens the port to every pod in it. |
networkPolicy.probeSources |
list | [] |
CIDRs allowed to reach the probed port. Empty means open, not blocked — the rule is then rendered with no from at all. Probes come from the IP of the worker node running the pod, which the chart cannot know; a list that misses it drops every probe and the pod never becomes Ready. |
networkPolicy.ingressController |
object | kube-system / app.kubernetes.io/name: rke2-traefik |
The controller allowed to reach the routed port once probeSources restricts it. The two keys render as one from entry, i.e. an AND — which is why this is not a line in allowedNamespaces, where it would open the port to every pod in kube-system. Default measured on the cluster, not assumed. |
networkPolicy.egress.extraPorts |
list | [] |
Additional outbound TCP ports, to any destination. The escape hatch from the egress cap, and the only alternative to switching the policy off. |
Operating notes
Routing: Ingress or Gateway API
Two mutually exclusive modes, and exactly one of them may be on. ingress.enabled ships true, httpRoute.enabled ships false.
| Rule | Why it is a hard failure and not a default |
|---|---|
ingress.enabled and httpRoute.enabled cannot both be true — the render aborts naming both value paths |
two data paths onto one Service under one hostname is a conflict at DNS level already: one name, two addresses, and which one a client reaches is luck. A migration runs over a second hostname (httpRoute.hostnames set explicitly while the Ingress keeps ingress.domain), never in parallel. |
httpRoute.parentRefs must not be empty — the render aborts |
a route without a parent attaches to no Gateway. The object is created, it is schema-valid, no controller rejects it, and it routes nothing: no event, no condition, and the only symptom is a hostname that does not answer. |
| the mode is a value, never auto-detected | .Capabilities.APIVersions is unreliable at template time, and an HTTPRoute without the CRDs fails at install time. A chart that guessed would sometimes render an object the cluster rejects and sometimes silently render nothing. |
What moves to the platform. In Gateway mode the chart renders no Gateway, no gatewayClassName, no Certificate and — deliberately — no implementation-specific policy object (ClientSettingsPolicy, ClientTrafficPolicy and their equivalents). Only HTTPRoute with parentRefs looks identical on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio, and keeping to that subset is what lets the cluster's implementation be chosen, or replaced, without touching the chart again. Class and certificate therefore belong to the Gateway and its listeners, which live in cluster-config. ingress.clusterIssuer is unused and unnecessary in this mode.
In Gateway mode the forward-auth escape hatch does not exist in the chart, and that is the sharpest consequence of the switch here. SearXNG has no login of its own; ingress.annotations is where this page documents putting one in front of it (Authentik and friends), and the Gateway API has no annotation channel at all. The chart renders no implementation-specific policy object on purpose, so "who may use this instance" becomes entirely the Gateway's question: a listener that is not publicly reachable, or that implementation's own auth filter, configured next to the Gateway. The Gateway-mode NOTES.txt says so, because "put auth in front of it via ingress.annotations" is advice that silently stops applying.
Two things are not affected. The rate limiter runs inside SearXNG against a Valkey (settings.limiter), not at the proxy. And SEARXNG_BASE_URL still follows ingress.domain, not httpRoute.hostnames — signed image-proxy URLs and the OpenSearch description keep naming that host whatever the route publishes.
Whether the route attached at all is only visible in its status, because no Kubernetes object reports a route that attaches to nothing:
kubectl -n <namespace> get httproute <release>-searxng-httproute -o yaml
Accepted: True and ResolvedRefs: True under status.parents[] is a working attachment. An empty status means no controller has looked at the route at all — usually a parentRefs pointing at a Gateway that does not exist, or a listener whose allowedRoutes does not admit this namespace.
ingress.domain is read twice
ingress.enabled: false removes the Ingress and frees clusterIssuer — and nothing else. The domain is read a second time, outside the Ingress template, to build SEARXNG_BASE_URL, and carries its own required() there. That read cannot be dropped: server.base_url is what SearXNG puts into the OpenSearch description, into the image-proxy URLs it signs and into every absolute link. Left empty it renders https:///, the pod goes green, and browsers are sent to a broken address.
With the Ingress off, reach the instance with a port-forward — and expect links on the page to still point at https://<ingress.domain>/.
settings.extra is the real configuration surface
The chart models six first-class keys and nothing else, on purpose: a chart that mirrors SearXNG's hundreds of engine options is out of date with the first upstream change and then lies about what the instance runs. Everything else goes into settings.extra in upstream's own key names and is merged over the generated block:
settings:
extra:
search:
default_lang: "de"
formats: ["html", "json"]
ui:
default_theme: simple
Merge semantics come from the same null-safe helper the security contexts use — not sprig's mergeOverwrite, which drops zero values: nested maps merge key by key, an explicit false or 0 wins, a null keeps the value from the block below (nulls never delete), and a scalar written where the base has a map is rejected by name instead of failing later as a template error. settings.extra stays open in values.schema.json because its schema is SearXNG's, not this chart's.
server.secret_key is deliberately absent from the file: it arrives as SEARXNG_SECRET from the Secret, so the signing key never lands in a ConfigMap.
Why all three probes use /healthz
/healthz is the only path SearXNG's bot detection lets through unconditionally (limiter.py filter_request() returns early on exactly this path). Measured in k3d against a reachable Valkey with the limiter on: / answered 429 while /healthz answered 200. A probe on / would therefore not merely risk a 429 — it would collect one the moment the limiter is switched on, and the NotReady pod would point at the application instead of at the bot detection. The handler is a fixed Response('OK'): no engines, no cache, no network, so liveness carries no external dependency (style guide §12.3).
Stated plainly against it: a green pod means the WSGI worker answers. It does not mean a search works, that any engine is reachable, or that this chart's settings.yml was the file that got loaded.
The limiter fails quietly without a Valkey
settings.limiter: true without secrets.valkeySecretRef is the uncomfortable middle outcome, measured rather than guessed:
- one ERROR at worker start,
searx.limiter: The limiter requires Valkey, …, and nothing after that - the pod becomes Ready and stays Ready
- every request is served normally —
/,/searchand/healthzall answered 200, with no rate limiting in effect
So the release is not rate limited, and the only trace is one line at the top of the log. NOTES.txt prints this case explicitly. Either set the Valkey ref or set the limiter back to false.
Authentication belongs on the Ingress
SearXNG has no login. Forward-auth is entirely an ingress-nginx matter and needs no chart change — put the auth-url / auth-signin / auth-response-headers / auth-snippet annotations under ingress.annotations (the example in values.yaml uses Authentik). Two consequences:
- The kubelet's probes go straight to the pod and never pass the Ingress, so an authentication outpost that is down cannot turn this release red. Good for availability; it also means a green pod says nothing about whether the login in front of it works.
- Anyone who adds this instance as a browser search engine sends their queries through that same login, which is why such a setup "suddenly stops working" after a session expires.
GRANIAN_HOST and the SEARXNG_* precedence trap
GRANIAN_HOST: "::" is the image's own default and was measured here: in k3d the pod is single-stack IPv4, granian logs Listening at: http://:::8080, and the kubelet's IPv4 probes are answered. On a cluster with an IPv6-disabled kernel granian would fail to bind — set 0.0.0.0 there. There is no SEARXNG_* alias for the bind address; the entrypoint maps only SEARXNG_PORT onto GRANIAN_PORT.
The precedence is the trap: SearXNG's own SEARXNG_* variables override the ConfigMap, so an env entry named SEARXNG_LIMITER beats settings.limiter. That is a way out if you need it and a trap if you forget it.
Rotating the signing key does not restart the pod
Helm never sees a OnePasswordItem's content, so it cannot hash it (style guide §9). The Deployment carries checksum/secret-ref and checksum/secret-ref-valkey over the rendered CRs, so pointing a *SecretRef at a different item rolls the pods; editing the value inside the same item does not — both are consumed as environment variables, which Kubernetes never refreshes in a running container.
kubectl -n <namespace> rollout restart deployment/<release>-searxng-deployment
A new key invalidates every user's saved preferences cookie, which is a logout-shaped event for the people using it.
A changed settings block does roll the pods: the Deployment carries checksum/config over the ConfigMap. Without it nothing would happen — settings.yml is a subPath mount, which the kubelet never refreshes, and SearXNG reads its configuration once at start.
The expected ownership warning
The emptyDir at /var/cache/searxng is owned root:977 (fsGroup only sets the group), so the entrypoint prints a multi-line
!!! WARNING
!!! "/var/cache/searxng" directory is not owned by "searxng:searxng"
and carries on. It cannot fix the ownership as a non-root process — which is the point — and does not need to: the directory is group-writable through fsGroup. Not a fault.
443 goes anywhere, and 6379 is the standard port rather than yours
Querying search engines is what SearXNG does, and A NetworkPolicy matches CIDRs and selectors and never hostnames, and this cluster's CNI (Canal — Flannel plus Calico) has no FQDN rule, so an egress rule can only ever name a port and not a destination. — so the 443 rule permits any destination and cannot be narrowed to an engine list.
6379 is the conventional Valkey port. This chart deploys no Valkey; secrets.valkeySecretRef points at an external one through a URL the chart never parses, so a Valkey anywhere else is blocked and belongs in networkPolicy.egress.extraPorts. Add 80 there too if an engine or proxy in use is plain HTTP.
The probed port is open inside the cluster by default
networkPolicy.probeSources is empty out of the box, so the rule for the port the probes address is rendered with no from selector at all — any pod the CNI routes to can reach it. Not from the internet (the port carries no NodePort), but this is not pod-level isolation, and reading the object as if it were is the mistake to avoid.
It is that way on purpose. The kubelet's liveness, readiness and startup probes originate from the IP of the worker node running the pod — an address no chart can know and must not guess. A CIDR that excludes the real node blocks every probe: the pod never becomes Ready, the rollout times out, and the symptom reads as a broken application rather than as a policy. Shipping open is the honest default; narrowing it means listing the worker node IPs yourself.
Note the companion trap: the moment probeSources is non-empty the rule is restricted, so everything else that needs the port has to be in the same rule. The chart adds the release's own namespace, ingressController and allowedNamespaces for exactly that reason.
The policy is only a boundary where the CNI enforces one
The API server accepts a NetworkPolicy whatever the cluster does with it. Where the CNI does not enforce policies, everything looks configured and nothing is filtered — which is worse than no policy, because it invites the assumption that something is being filtered. Verify by connecting from an unrelated namespace rather than by reading it off the manifest.
Measured for this cluster: the CNI is Canal (Flannel for the network, Calico for policy), 6/6, and it does enforce. Measured for CI: k3s brings its own policy controller and enforces too — but it exempts the kubelet's probe traffic, so a blocked probe path does not turn the install-test red. What CI does catch is anything the application itself needs.
Upgrades and backup
- Nothing to back up.
settings.ymlcomes from the ConfigMap, the signing key from the Secret, and both writable paths are emptyDirs that refill on their own. App-version upgrades and rollbacks carry no data risk. /tmpis not optional:searx/cache.pyfalls back totempfile.gettempdir()andwebapp.pyconnects to two SQLite caches before the first request. A read-only/tmpis not a degraded cache, it is a container that never starts./var/cache/searxngis mounted although nothing in this version writes there — it is the path the application declares as its data directory, and the day a release starts using it a read-only root filesystem would turn that into a crash loop.
Renovate
SearXNG publishes no semver line: every tag is <date>-<commit> and latest moves daily. appVersion is the exact tag, and the +up suffix normalizes the dash to a dot as Versioning requires.
The chart's customManagers block overrides versioning with an anchored regex in which the commit hash sits outside every capture group. That is the lesson this chart paid for: Renovate parses major/minor/patch/build/revision with Number.parseInt, so a hash captured as build becomes whatever digits lead it — 9fea41204 is 9, 777ba8fa4 is 777, a hash starting with a letter is NaN. Measured on renovate 41.173.1 and 44.48.0, that makes Renovate offer an image pushed earlier the same day as an update to a newer one — a downgrade dressed as a routine date bump, which a review waves through and automerge then carries to main. A capture group is a claim about how the value is compared; parts of a tag that carry no ordering belong outside every group. prerelease is not the way out — it sorts correctly but marks every tag unstable, handing the chart to ignoreUnstable. See Renovate and Lessons learned.
CI
ci/searxng/ holds test-values.yaml (searxng.ci.local, an op:// ref against the stub CRD, and deliberately non-default settings) and fixtures.yaml (the one dummy Secret the operator would otherwise materialize). Two things are deliberate there:
- The install runs the default path — limiter off, no Valkey — because switching the limiter on without a reachable Valkey produces a Ready pod that logs an error, which makes the test slower without making it stricter.
- The fixture's
secret-keyis a real random-looking string, notultrasecretkey:webapp.pyexits 1 on the placeholder, so a careless fixture would be a crash loop rather than a passing install-test. - The test values set
instanceName,safeSearchand anextrablock with a nested map, so the install-test is the place that proves the merge joins rather than replaces (search.safe_searchhas to survive next tosearch.default_lang).
The install-test runs on every PR that touches the chart — see CI/CD.
The install-test stays in Ingress mode; the route is held at render level. The install-test installs exactly one configuration per chart and the two modes are exclusive, so one had to be picked. cyberchef is the chart that pays that price and installs in Gateway mode against the real Gateway-API CRDs (ci/gateway-api-crds.sh, pinned, applied --server-side), which is where a malformed parentRefs or backendRefs is caught. Spending it a second time here would have dropped this chart's Ingress from the install for a schema the repository already exercises. Instead ci/httproute/searxng.yaml is a render-only overlay that switches this chart into Gateway mode for ci/run-httproute-match-test.sh, which asserts that every backendRefs entry names a Service and a port the same render creates and that parentRefs is not empty. A chart that has an overlay and renders no route fails that test, so the assertion cannot go vacuum-green. Neither half proves that traffic flows: no Gateway controller runs in CI. See CI/CD.
See also
- Chart style guide — the rules this chart implements
- Versioning — what a bump of
1.0.6+up2026.9.13.3b8009065means - Renovate — why this chart carries a
versioningoverride - Lessons learned
- Chart index
helm-charts wiki
How we work
How it is built
How charts are written
Charts
- apache-tika
- bentopdf
- bulwark
- calibre-web-automated
- convertx
- cyberchef
- freesailarr
- garage
- gotenberg
- homeassistant
- keycloak
- matrix-synapse
- minecraft-router
- minecraft-server
- outline
- paperless-ngx
- patchmon
- rallly
- rss-bridge
- samba
- satisfactory
- searxng
- stalwart
- sure
- teamspeak-server
- template-chart
- urlaubsverwaltung
- valkey
- voucher-vault
- ytdlp-web-player
- zipline