Skip to main content

Overview

SearXNG is a privacy-respecting metasearch engine: it holds no index of its own, forwards each query to roughly 200 upstream engines, and merges the results while keeping no profile and setting no tracking cookies. This template deploys it as a stateless workload on port 8080, with the JSON API switched on, plus a small bundled datastore that backs the optional rate limiter. The JSON API is the reason this template exists. GET /search?q=...&format=json returns merged web results as JSON — no API key to obtain, no per-query bill, and no vendor rate limit — which makes it a search backend an AI agent can call directly. formats ships as [html, json], so the API is live on a default install alongside the human UI. The install is private by default: publicAccess.enabled is false and limiter.enabled is false. Those two defaults belong together, and choosing differently is the one real decision this template asks of you — see Choosing an Access Shape.

Architecture

  • SearXNG server — A standard (stateless) workload serving the UI and the API on port 8080. replicas instances run behind the platform load balancer with no session affinity, because there is no server-side session to pin: preferences live in a cookie in the user’s own browser.
  • Redis and Sentinel (optional, on by default) — The redis template as a subchart, pinned to one master and one sentinel. It holds the rate limiter’s counters and the shared state a multi-replica install needs. It is not a result cache — search results are never stored there.
  • Settings — The rendered settings.yml is mounted read-only at /etc/searxng/settings.yml from a template-created secret.
  • Signing key — Supplied by you as a prerequisite secret and injected as an environment variable, so it is never written into the rendered configuration and never enters the Helm release.
  • No volume set, no database, and no login. Everything durable is either your prerequisite secret or a cookie in a user’s browser.

What Gets Created

  • Standard SearXNG Workload{release}-searxng, serving the UI and API on port 8080 with replicas replicas and configurable CPU and memory.
  • Stateful Redis and Sentinel Workloads{release}-redis on 6379 and {release}-sentinel on 26379, created only when redis.enabled is true. Both are in-memory; neither mounts a volume.
  • Settings Secret — An opaque secret ({release}-searxng-settings) holding the rendered settings.yml.
  • Start-Script Secret — An opaque secret ({release}-searxng-startup) holding the container’s start-up script, created only when the bundled datastore is enabled.
  • Identity & Policy — An identity bound to the SearXNG workload and a policy granting it reveal on exactly the secrets it reads: the settings secret, your prerequisite signing key, the start-up script, and the datastore password when redis.redis.auth.password.enabled is true. The Redis subchart brings its own identity, policy, and config secret.
No volume set is created anywhere in this template, so an uninstall leaves no storage behind.
This template does not create a GVC. You must deploy it into an existing GVC.

Prerequisites

One opaque secret must exist before you install. It holds server.secret_key, which signs preference cookies and CSRF tokens. Because it is a secret you create and reference by name, the value never passes through Helm values and never lands in the release.
Then set secretKey.secretName to the name you used. Installing with an empty secretKey.secretName fails at render time.
Create the secret before installing. A name pointing at a secret that does not exist installs “successfully” and then wedges silently: every resource reports created, the workload never becomes ready, and cpln logs returns zero lines because no container ever starts. The only diagnostic is status.versions[].message:
It names the missing secret: The secret <name> no longer exists. Workload updates are paused until the secret is added or the reference to the secret removed. The command is get-deployments — plain cpln workload get has no versions key at all. Creating the secret recovers the workload on its own, measured here at 9 minutes; cpln workload force-redeployment {release}-searxng --gvc {gvc} skips the wait.
Rotating the signing key invalidates every saved preference. User preferences live in a cookie signed with this key, so replacing it discards every stored setting in every browser. Treat it as write-once rather than something to rotate on a schedule, and keep a copy outside Control Plane — reinstalling against the same secret loses nothing.
Nothing else is required: no cloud account, no domain, no external database.

Installation

To install, follow the instructions for your preferred method:

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Choosing an Access Shape

The rate limiter and the JSON API pull in opposite directions, and this is the decision to get right before you publish anything. The limiter is bot detection, not a simple request counter: it inspects the request and rejects anything that does not look like a real browser. A plain curl, a Python HTTP client, or an AI agent therefore gets 429 Too Many Requests on every route it cares about — /search, /search?format=json, and /config alike — while a browser-shaped request is served normally and then throttled at a burst ceiling of 15 searches. Both halves were measured: with the limiter on, 25 plain-client requests returned 429 every time, and 25 browser-shaped searches returned 200 fifteen times and 429 for the rest. Turning the limiter off makes the JSON API usable by any client. It also means nothing throttles the instance, which is fine behind a closed firewall and a problem on the open internet. The combination to avoid is public with the limiter off: an open, unthrottled metasearch instance that anyone can drive. Upstream engines respond to the traffic it attracts by throttling or blocking your egress address, and the result is a slow decline in result quality rather than an outright failure. That degradation is real and was observed during testing — after roughly 150 rapid queries, DuckDuckGo, Brave, and Startpage all began answering with CAPTCHA and “too many requests” responses, and result counts fell from 20 to single digits before recovering. SearXNG has no login of any kind, so the limiter is the only control a public instance has. Two constraints the chart enforces at render time: limiter.enabled: true requires redis.enabled: true, and redis.redis.replicas must stay 1.

Reaching the UI

A default install is private, so a port-forward is the operator’s path to it. This works even when the workload is closed to the internet and to the GVC (internalAccess.type: none), because the tunnel does not go through the data-plane firewall:
Then browse http://localhost:8080. Note that cpln port-forward is a top-level command, not a cpln workload subcommand. To publish the UI instead, upgrade with publicAccess.enabled: true and limiter.enabled: true, then read the assigned *.cpln.app address from status.canonicalEndpoint:
Firewall changes are not instantaneous. Publishing the endpoint took 20 seconds to start returning 200 in testing, and closing internal access took about 50 seconds to start refusing in-GVC callers. Allow up to a couple of minutes and re-test rather than concluding the knob did not work. A port-forward keeps serving throughout.
A port-forward is unreliable while replicas are being replaced — during a scale-down or a redeployment the tunnel can follow a replica that is going away. Re-run it once the deployment has settled.

Configuration

The default values.yaml for this template:

Image and resources

  • image — The SearXNG server image. Pin a concrete tag.
  • replicas — Number of instances. 1 is the proven single shape; 2 or more form a load-balanced tier that shares limiter state through the bundled datastore. At two replicas, a rolling restart served 300 of 300 requests and an abrupt replica loss served 75 of 75, with the replacement serving 11 seconds later.
  • resources.minCpu / resources.maxCpu / resources.minMemory / resources.maxMemory — CPU reservation and limit, memory reservation and limit for the SearXNG container.

Search Behavior

  • instanceName — The name shown in the UI and returned by /config.
  • formats — Output formats the instance will serve. Allowed values are html, json, csv, and rss; upstream defaults to html only, and this template ships [html, json]. A format that is not listed is refused with 403, so removing json disables the API.
  • imageProxy — When true (default), result images are relayed through this instance instead of being hotlinked from third parties, so a user’s browser never contacts the origin. On a public instance that also makes it an open image relay.
  • baseUrl — Set only when the instance sits behind a custom domain (for example https://search.example.com). Empty derives the URL from the request, which is correct for the canonical *.cpln.app endpoint. It drives the opensearch.xml document browsers use to add the instance as a search provider.
  • limiter.enabled — The bot-detection rate limiter. Off by default; requires redis.enabled. See Choosing an Access Shape before turning it on.
  • extraSettings — Merged verbatim into settings.yml over the keys above, so anything in the upstream settings reference is reachable — engine selection, safe_search, outgoing timeouts. An override of a key this template also sets wins cleanly instead of producing a duplicate YAML key.

Signing Key

  • secretKey.secretName — The name of your prerequisite opaque secret, whose whole payload is the signing key. Every replica reads the same one, which is what lets any replica verify any user’s preference cookie. The chart grants the workload reveal on exactly that secret.

Access

  • publicAccess.enabled — When true, the UI and API are served over HTTPS on the automatically assigned *.cpln.app endpoint, unauthenticated. When false (default), external requests are refused at the edge with 403.
  • internalAccess.type — Internal firewall scope of the workload:
  • internalAccess.workloads — Workload links (//gvc/GVC_NAME/workload/WORKLOAD_NAME), used only with workload-list.
Outbound access is always open to 0.0.0.0/0 and is not configurable: querying upstream engines over the public internet is the entire function of the service, and narrowing it makes every engine fail.

Redis

The bundled datastore exists for the limiter and for shared state across replicas. It stores no search results and nothing durable.
  • redis.enabled — When false, no datastore is deployed and SearXNG runs standalone. limiter.enabled must then stay false; the chart refuses to render the combination, because without the datastore SearXNG would install no limiter at all while its configuration still claimed one.
  • redis.redis.replicasMust stay 1, and the chart rejects anything else. SearXNG’s client is not Sentinel-aware and has to reach the master directly, so extra replicas would serve read-only connections and silently break the limiter. One sentinel is required by the Redis chart’s boot-time master discovery and cannot be scaled to zero.
  • redis.redis.auth.password.enabled / redis.redis.auth.password.value — When enabled, the datastore requires AUTH and the password is wired into the connection URL through a template-created secret. Change the placeholder value if you turn this on.
  • redis.redis.image / redis.sentinel.image / redis.redis.resources / redis.sentinel.resources — Image and CPU and memory bounds for the two datastore workloads.
With the limiter on, a SearXNG container that starts while the datastore is unreachable refuses to start rather than coming up silently unthrottled: the deployment reports a container exit instead of serving. If a fresh install with limiter.enabled: true never becomes ready, check the {release}-redis workload first. With the limiter off, the same container logs a warning and starts normally.

Using the JSON API

From another workload in the same GVC, using the fully-qualified internal name:
The bare short name does not resolve to this workload — the fully-qualified .cpln.local form is required. The response carries the merged results, the engines that answered, and an unresponsive_engines list naming any that timed out or returned a CAPTCHA. Useful query parameters include categories (for example general, images, news), language, time_range, and pageno; the full set is in the upstream search API reference. Keep limiter.enabled: false for this: the limiter rejects requests that do not look like a browser, which includes plain curl and most HTTP client libraries. If the instance must be public and callable by an agent, the agent has to send browser-shaped headers — a realistic User-Agent plus Accept, Accept-Language, and Accept-Encoding — and it will still be throttled after 15 searches in the limiter’s burst window.

Connecting

A fresh install typically has all three workloads ready within about half a minute of the install command returning.

Important Notes

  • There is no authentication of any kind. With publicAccess.enabled: true the UI, the API, /config, and /stats are all served to anyone who finds the endpoint. Turn limiter.enabled on before you publish.
  • The limiter blocks the JSON API for non-browser clients. Plain curl and HTTP client libraries get 429 on /search while the limiter is on. Pick one of the two shapes in Choosing an Access Shape deliberately.
  • /config and /stats are unauthenticated and go public with the UI. /config lists your engine and plugin configuration. Setting extraSettings: {general: {enable_metrics: false}} stops collecting the data behind /stats.
  • /metrics is not open, and cannot be opened without a password. Upstream gates it on general.open_metrics, which is empty by default, so the route returns 404 as shipped. Setting it through extraSettings enables the route and requires that password over HTTP basic auth.
  • Create the signing-key secret before installing, and treat it as write-once. A missing secret wedges the deployment with zero log output; rotating it invalidates every saved preference cookie.
  • redis.redis.replicas must stay 1, and limiter.enabled requires redis.enabled. Both are enforced at render time, so a bad combination fails the install rather than shipping a silently unlimited instance.
  • The datastore holds no durable data. If it restarts, limiter counters reset and nothing is lost, because search results were never stored there.
  • Going public throttles your own results. Upstream engines answer heavy or bot-like traffic with CAPTCHAs and rate limits, which shows up as fewer results rather than an error. A limiter and a modest instance are what keep result quality up.
  • The first helm upgrade after an install can re-apply resources that did not change, including the bundled datastore, which bounces those workloads once. Values were byte-identical across the re-apply in testing, and later upgrades of the same release report everything unchanged. At replicas: 1 budget a brief blip on that first upgrade.
  • Firewall changes take effect after a delay — flipping publicAccess.enabled or internalAccess.type can take from twenty seconds to a couple of minutes to be enforced after the upgrade reports success.
  • Install into a single-location GVC unless you mean otherwise. A workload runs in every location its GVC has, so extra locations multiply both the instance count and the outbound query volume upstream engines see from you.
  • Uninstalling removes everything the template created — workloads, secrets, identity, and policy, with no volume sets left behind. Your own signing-key secret survives, so a reinstall against the same name keeps preference cookies valid.

External References

SearXNG Project

Project home, public instance list, and overview

Settings Reference

Every key that can be set through extraSettings

Server Settings

The server.* block, including the limiter and image proxy

The Limiter

How bot detection and rate limiting work upstream

Search API

Parameters and response format for /search

SearXNG Template

View the source files, default values, and chart definition