The signed Helm chart: one pod on SQLite, or several on PostgreSQL and Redis behind a disruption budget. Installed and upgraded under load on a real cluster in CI, and it refuses setups that can't work.
Values may be added; none is renamed or removed except through the deprecation process. Chart X.Y.Z runs QueryAPIGate X.Y.Z. It installs no PostgreSQL or Redis of its own, and its NetworkPolicy stays off until you give it your cluster's rules. Requires Kubernetes 1.25 or later, with Helm 3 or 4.
config values, configAsCodeOne pod, with queryapigate.db on a 1 Gi PersistentVolumeClaim. The claim is kept on helm uninstall. Reinstall under the same name and namespace and it picks the store up again, keys and all. Upgrades on SQLite replace the pod instead of rolling it, so expect a few seconds of downtime.
kubectl create namespace queryapigate kubectl -n queryapigate create secret generic queryapigate \ --from-literal=QUERYAPIGATE_API_KEY=<a long random key> \ --from-literal=QUERYAPIGATE_SECRET_KEY=<a Fernet key> helm install queryapigate oci://ghcr.io/anantharajuc/charts/queryapigate --version 0.47.0 \ -n queryapigate --set existingSecret=queryapigate --wait
The pods roll one at a time, and a new pod must be ready before an old one stops (maxUnavailable: 0). The PodDisruptionBudget keeps all but one up during node drains. Use managed PostgreSQL and Redis in production; deploy/kubernetes/dev-dependencies.yaml starts throwaway ones for a trial.
store: {type: postgres}
replicas: 3
existingSecret: queryapigate # + QUERYAPIGATE_DATABASE_URL and QUERYAPIGATE_REDIS_URL
redis: {inExistingSecret: true} # the chart can't see inside your Secret
events: {enabled: true} # live events for many clients
mcp: {enabled: true} # the MCP server for AI agents
config:
QUERYAPIGATE_JSON_LOGS: "1"helm install and helm upgrade fail without touching the cluster.
max_concurrent would be counted per pod),store.type: postgres without a database URL,config, where it would sit in plain text in the Deployment,values.schema.json. A misspelt replica=3 fails with additional properties 'replica' not allowed.Error: UPGRADE FAILED: store.type sqlite keeps the store in one file on one volume - it runs one pod. For several, set store.type: postgres (QUERYAPIGATE_DATABASE_URL in the secret) and Redis.
Non-root (UID 1000), read-only root filesystem, every capability dropped, RuntimeDefault seccomp, no service account token (QueryAPIGate never calls the Kubernetes API). It writes only to /data and /tmp.
$ kubectl exec deploy/queryapigate -- sh -c 'id; touch /x' uid=1000(app) gid=1000(app) groups=1000(app) touch: cannot touch '/x': Read-only file system
/metrics needs a token by default (metricsAuth): the chart creates one in its own Secret, keeps it across upgrades, and the ServiceMonitor sends it.| Concern | Values |
|---|---|
| Secrets | existingSecret (recommended: sealed-secrets, External Secrets, Vault), or secret.create + secret.values |
| Other settings | config: any QUERYAPIGATE_* or OTEL_* |
| Routing | ingress.* sends /events to the events pods and everything else to the API pods, and sets QUERYAPIGATE_TRUST_PROXY=1. mcp.ingress.host gives MCP its own host. The defaults are ingress-nginx no-buffering annotations; replace them for another controller. |
| Image | image.digest: pull by the digest you verified |
| Observability | serviceMonitor.enabled, metricsAuth, tracing through OTEL_EXPORTER_OTLP_ENDPOINT |
| Network | networkPolicy.enabled with ingressFrom and egress. List your databases, Redis and SIEM there, or queries can't reach them. |
| Scale | autoscaling.enabled (CPU-based, PostgreSQL shape only; 2-6 replicas by default) |
| Secrets managers | vaultToken.enabled (a projected, short-lived token for Vault only); serviceAccount.annotations for IRSA |
| Files by path | extraVolumes / extraVolumeMounts for SIEM certificates or export directories, since the root filesystem is read-only |
The same helm upgrade with the new --version. In the guide's run, a pod called /q/tables every 50 ms throughout the upgrade: 1,690 answered, 0 failed. Upgrade one minor version at a time, and read its changelog entry first.
tests/test_helm.py: refusals, hardening, routes, and the plain manifests kept in step). Then it is installed on a kind cluster in the PostgreSQL shape with three pods, events and MCP, and upgraded from the previous release's chart to the current code while a pod keeps calling it. A single failed request fails the build. The same run checks that /metrics answers 401 without the chart's token.configAsCode.enabled with configAsCode.file (or existingConfigMap) puts connections, queries, roles and keys in one file in git, with no secrets: passwords are ${VAR} references and keys are stored as secret_sha256. The first new API pod applies it as it starts, once per file. A change to the file rolls the pods. A file that can't be applied stops the rollout at its first pod, while the old pods keep serving. locked: true restricts what the file manages to changes through the file, so a Console edit gets 409 config_managed. Argo CD or Flux syncing the chart is then the deploy (guide 55).
The same works for the -h2 image and the chart. Then set image.digest to the verified digest (guide 59).
cosign verify ghcr.io/anantharajuc/queryapigate:0.47.0 \ --certificate-identity https://github.com/AnanthaRajuC/QueryAPIGate/.github/workflows/docker.yml@refs/tags/v0.47.0 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com
kubectl rollout restart on each Deployment (-events and -mcp too). Changes to config, or to a Secret the chart makes, roll the pods on helm upgrade.kubectl exec deploy/queryapigate -- queryapigate backup /tmp/backup.db --force, then kubectl cp the file out. To restore, scale to 0, put the file on the claim from a one-off pod, remove -wal and -shm, and scale back to 1. On PostgreSQL, use your database's own backups.deploy/kubernetes/queryapigate.yaml is the same deployment as plain manifests, kept in step by a test.Moving between shapes is a configuration change. This is what each step adds; the step for this page is highlighted.
QUERYAPIGATE_API_KEY=…
QUERYAPIGATE_SECRET_KEY=…
One process or container, with the store as SQLite on a volume. The key is required in a container; the secret key encrypts connection passwords at rest.
QUERYAPIGATE_DATABASE_URL=postgres://…
QUERYAPIGATE_REDIS_URL=redis://…
A shared PostgreSQL store and Redis, so any instance answers any request and limits are counted once. Run queryapigate events behind the proxy for live events. Guide 42 →
store.type: postgres · replicas: 3
existingSecret: queryapigate
The same two URLs and the key live in one Secret; the chart refuses several pods on SQLite, several without Redis, PostgreSQL without a database URL, and secrets put in config. Guide 54 →