Deploy on Kubernetes
SCRUBR ships a Helm chart published as an OCI artifact on every release. It runs
the hardened container image (non-root, read-only rootfs), serves a ClusterIP on
:8080 with /healthz probes, and scales from a single pod to a Redis-backed HA
cluster.
Install
helm install scrubr oci://ghcr.io/scrubr-dev/charts/scrubr --version X.Y.Z
This deploys one replica with the default config (a dry-run reverse proxy to OpenAI). Point your app at the in-cluster Service:
http://scrubr.<namespace>.svc:8080/openai/v1/chat/completions
Configure
Put your SCRUBR config under config: in a values file — its contents become the
mounted scrubr.yaml:
# my-values.yaml
config:
routes:
- { listen_path: /openai, upstream: https://api.openai.com, profile: openai }
profiles:
openai:
scan_paths: ["messages[].content"]
stream_paths: ["choices[].delta.content"]
masking:
mode: enforce
rules:
- { name: email, type: EMAIL, pattern: '[\w.+-]+@[\w.-]+\.\w+', priority: 50 }
helm install scrubr oci://ghcr.io/scrubr-dev/charts/scrubr --version X.Y.Z -f my-values.yaml
Prefer to manage the config yourself? Set existingConfigMap to a ConfigMap that has
a scrubr.yaml key.
High availability
For multiple instances sharing session-scoped pseudonyms, enable HA. The chart
switches to a StatefulSet so each pod gets a stable ordinal, fed to SCRUBR as a
distinct node_id — the id-space partition that keeps concurrent nodes from
colliding. Pods share state through Redis, encrypted at rest.
flowchart TD
SVC["Service :8080"] --> P0["scrubr-0 · node_id 0"]
SVC --> P1["scrubr-1 · node_id 1"]
SVC --> P2["scrubr-2 · node_id 2"]
P0 & P1 & P2 --> R[("Redis<br/>encrypted session maps")]
subgraph SS["StatefulSet (pod ordinal → SCRUBR_NODE_ID)"]
P0
P1
P2
end
Turnkey: bundled Redis
helm install scrubr oci://ghcr.io/scrubr-dev/charts/scrubr --version X.Y.Z \
--set ha.enabled=true --set replicaCount=3 \
--set redis.enabled=true --set redis.password=$(openssl rand -hex 16) \
--set sessions.encryptionKey=$(openssl rand -hex 32) \
--set config.masking.scope=session
This deploys a single dependency-free Redis (official image) alongside SCRUBR — fine
for many setups. For durability, add --set redis.persistence.enabled=true.
Production: external/managed Redis
Point at your own Redis (managed service, or a clustered/HA Redis) and leave the bundled one off:
helm install scrubr oci://ghcr.io/scrubr-dev/charts/scrubr --version X.Y.Z \
--set ha.enabled=true --set replicaCount=3 \
--set redis.url=rediss://scrubr:pass@redis.internal:6379/0 \
--set sessions.encryptionKey=$(openssl rand -hex 32) \
--set config.masking.scope=session
HA also adds a PodDisruptionBudget and soft anti-affinity; set
autoscaling.enabled=true for an HPA. The credentials (Redis URL + at-rest key) are
held in a Secret and injected via secretKeyRef, never as plain pod env.
Requires Kubernetes ≥ 1.28 (the
apps.kubernetes.io/pod-indexdownward-API label). The Redis URL/key can also live in your own Secret — setsessions.existingSecretto a Secret with keysredis-urlandencryption-key.
Verify
helm test scrubr # hits /healthz from an in-cluster pod
kubectl get pods -l app.kubernetes.io/name=scrubr
Without Helm
The same cluster wiring works via environment variables on any orchestrator —
SCRUBR_NODE_ID, SCRUBR_REDIS_URL, SCRUBR_ENCRYPTION_KEY, SCRUBR_SESSION_BACKEND
override the config's sessions block. See the
configuration reference.