Quick Start
A Sidecar in front of a Postgres, in four steps. At the end a normalpsql
reaches the database through the relay and the email column comes back
redacted, with no change to the client, the query or the database.
1
Deploy a Postgres to front
Skip this if you already have a database to point at. This one is a
throwaway with a password in plain sight, seeded with two rows.
postgres.yaml
postgres.yaml
2
Write a values file
config is the Sidecar’s own configuration document: one listener naming
the upstream, and one rule masking email addresses in whatever comes back.
laneServices publishes the listener to the cluster — the chart publishes
only the admin port on its own.values.yaml
values.yaml
3
Install the chart
lane ready line per listener, naming the upstream it resolved:1/1 Running has already answered /healthz on the admin
port, so readiness means the relay is serving.4
Query through it
-h postgres and the addresses come back in the
clear: the rows are unchanged in the database, and the relay rewrites them
on the way out.Every statement and every mask is on the audit trail, which audit.file: '-'
sent to the container log:A Sidecar can take its whole configuration from the Control Plane handshake instead of a file, in which case no
config attribute is set at all. See Control Plane.kubectl delete namespace hoop.
Helm Install
To install the latest version in a new namespace (example:hoop). Issue the command below:
A release named
hoopsidecar collapses to the bare name rather than repeating it.
Overriding values
It is possible to add new attributes or overwrite an attribute from a basevalues.yaml file. In the example below a specific image tag is pinned and the admin Service is turned off.
Sidecar Configuration
Theconfig attribute is the Sidecar’s own configuration document: protocols, guardrails, masking, audit, PII entities and AI analysis are all decided here, not by chart attributes. The chart renders it into a ConfigMap, mounts it at /etc/hoop-inspect/config.yaml and passes that path in HOOP_SIDECAR_CONFIG.
config rolls the pods on helm upgrade. A policy change is an upgrade, never a rebuild. That checksum only covers what this chart renders — see Using an existing ConfigMap for the case it cannot see.
Without a license the Sidecar caps guardrail and masking at one rule each and says so on its first line of output. See License.
Using an existing ConfigMap
To manage the document outside the chart — a GitOps repository, a sealed secret, an operator — pointexistingConfigMap at it. The key must be config.yaml.
config and existingConfigMap are mutually exclusive; setting both is refused.
Validating the configuration
Nothing needs to be running. The validator builds every lane and reports every problem in one run:config:, with that key stripped — not the values file.
Running as Sidecar
Everything above deploys the relay as a workload of its own, reached over the network. It can instead run as a container inside the pod it protects, reached over loopback. No chart is involved: you add a container to a Deployment you already own, using the raw image.hoophq/hoopsidecar runs hoop start sidecar as its command and already
points HOOP_SIDECAR_CONFIG at /etc/hoop-inspect/config.yaml, so mounting
the ConfigMap there is the whole wiring. No command, no env.
deployment.yaml
deployment.yaml
address already in use.
Why initContainers
A container listed under initContainers with restartPolicy: Always is a
native sidecar: the kubelet starts it before any application container and
stops it after the last one exits. Put the relay under containers: instead
and both ends of the pod’s life are a race — the application can open a
connection before the lane is listening, and can lose one while it is still
finishing work at shutdown.
Native sidecars are on by default from Kubernetes 1.29 and stable in 1.33. On anything older, list the relay under
containers: and give the application a retry on connect.Probes
The kubelet does not join the pod’s network namespace: anhttpGet or
tcpSocket probe is dialled from the node against the pod IP. A probe
against an admin server bound to 127.0.0.1 fails:
127.0.0.1 either way — only the
admin server moves.
An
exec probe runs inside the container, so it does reach loopback. The
default image has a shell and no HTTP client, which is enough; the
-distroless flavour has no shell and cannot run one at all.
Sharing a Socket with Envoy
The pattern above puts the Sidecar directly in front of the application. When Envoy sits in front instead — see Command Line — and the lane uses a unix socket rather than a published port, Envoy and the Sidecar need a shared, writable directory. No chart is involved here either; this is the same raw image run as a second container in a pod you already own.fsGroup on the pod’s securityContext replaces the Docker Compose init-container chown step: the kubelet applies it to the emptyDir before any container starts. Set it to Envoy’s gid and both sides can use the directory.
Mount the config as a ConfigMap and set HOOP_SIDECAR_CONFIG instead of passing a flag. The Compose equivalent of this setup, including the exact permission traps to watch for, is in Setting up the socket directory.
Chart Configuration
Everything below is a chart attribute rather than part of the Sidecar’s own configuration document: how the pod is built, what it is published as, and where it is scheduled.Image Configuration
By default the chart pullshoophq/hoopsidecar:latest, which is the Ubuntu flavour. To pin a version or switch flavours, use the image attribute section.
The chart sets no command: the image’s own entrypoint runs the relay and reads the configuration from
HOOP_SIDECAR_CONFIG.
Container Images covers what each flavour contains.
Admin Port and Probes
The chart declares exactly one port: 19000, the admin server. The same number is written into the Deployment, the Service and both probes, and it is not configurable.config.admin.listen therefore has to be 0.0.0.0:19000. A configuration that omits it or moves it is refused while the chart renders, because the Sidecar disables the admin server when the address is empty and the probes would otherwise poll a closed port and fail every pod:
Service
The chart’s Service publishes the admin port and nothing else.Exposing lanes
Lane ports are not declared on the pod — acontainerPort is informational in Kubernetes, and a lane bound to a unix socket has no port at all. Publishing them is laneServices, a map of Services, one Kubernetes Service per entry:
--set laneServices.public.type=LoadBalancer, which a list indexed by position does not.
Each entry is named <release>-hoopsidecar-<key> unless you set name, and carries the chart’s selector labels so it reaches that release’s pods and no other’s.
name is required on every port once an entry has more than one, and an entry that is enabled with no ports is refused — a Service with no port reaches nothing.
Ports here are literal. The chart does not read them from config.listeners, so keep the two in step: a Service pointing at a port nothing binds is accepted by Kubernetes and reaches nothing.
The admin Service is separate and unaffected: always ClusterIP, always 19000. A hand-written Service still works for anything laneServices does not cover — the selector is app.kubernetes.io/name: hoopsidecar plus app.kubernetes.io/instance: <release>.
These are the conventional port numbers, and what the examples in these docs bind:
Control Plane
To take the running configuration from a Control Plane, set the URL and the token from the Sidecar’s registration and leaveconfig unset.
License
Without a license, guardrails and masking are capped at one rule each. The value is a path or the document itself — a value starting with{ is read as the license — so a mounted Secret and a Helm value are the same field.
Extra Environment Variables
The chart sets the four variables the Sidecar reads:HOOP_SIDECAR_CONFIG, HOOP_LICENSE, HOOP_CONTROL_PLANE_URL and HOOP_SIDECAR_TOKEN. Anything else a configuration references — an analyzer provider’s API key, for instance — goes in extraSecret, which becomes a second Secret attached with envFrom.
Extra Volumes
For what the configuration document references by path: an upstream CA, a downstream certificate, an analyzer credential, a license file.Service Account
Withcreate: true the chart makes one named after the release. With create: false, name points at an account that already exists — the shape GKE Workload Identity wants, so an AI risk analyzer reaches Vertex with no credential on disk.
Replicas and Deployment Strategy
replicas is safe. Each replica is an independent proxy holding no shared state, and the Service spreads connections across them. Sessions are per-connection, so a replica going away drops the connections it was carrying and not the others.
The strategy defaults to
RollingUpdate rather than Recreate. This process sits in the data path between a client and its database, and Recreate would close every connection on every configuration change with nothing listening in between.Computing Resources
The chart sets no requests or limits by default. The Sidecar’s cost scales with concurrent connections and with what each lane does — detection and masking read the response body, an AI analyzer adds a network call per statement shape.Node Selector
This configuration describes a pod that has a node selector,disktype: ssd. This means that the pod will get scheduled on a node that has a disktype=ssd label.
See this documentation for more information.
Tolerations
See this article explaining how to configure tolerationsNode Affinity
See this article explaining how to configure affinity and anti-affinity rulesAnnotations
Running Several Sidecars
Two upstreams, two releases, one namespace:app.kubernetes.io/instance, so neither release adopts the other’s pods.
The alternative is one release with several entries under config.listeners. Pick by blast radius: separate releases give each upstream its own rollout, its own resource limits and its own audit stream; one release is fewer things to operate and one place to read.
Naming
nameOverride changes the base the resource names are built from; fullnameOverride replaces the whole name and drops the release prefix.