AI Gateway inference Helm values
Configure the AI Gateway inference chart with these Helm values.
This reference lists values declared in the chart's default values.yaml.
Templates can also accept optional settings and inherited global values.
Chart: ai-gateway-v2. Chart version: 0.22.0. Generated from the platform
source at e71926418e98c7598815d0abf07039d3c1646f21.
Values
replicaCount
Type: int
Default: 2
Number of replicas for the deployment. Ignored (the replicas: field is OMITTED
entirely from the rendered Deployment) when replicas.managedExternally is true,
so an external autoscaler's writes to replicas survive helm upgrade.
replicas.managedExternally
Type: bool
Default: false
When true, the chart never writes a replicas: field on the Deployment at all —
not even the last value it rendered. Helm's own three-way merge would otherwise
re-assert replicaCount on every upgrade and fight an external scaler (HPA/KEDA)
that manages replicas out of band.
image.repository
Type: string
Default:
"image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/sprout"
Container image repository. Enterprise build produced by _release-image.yml
(ko, -tags=enterprise), then promoted to the platform tag by release.yml.
Served to customers via Replicated's image proxy. task version-bump rewrites
the trailing :<tag> via a regex anchored on
ghcr.io/stacklok/stacklok-enterprise/<name>: — that anchor still matches
inside this longer proxy URL.
image.tag
Type: string
Default: ""
Image tag (defaults to appVersion if empty)
image.pullPolicy
Type: string
Default: "IfNotPresent"
Image pull policy
imagePullSecrets
Type: list
Default: []
Extra image pull Secrets to apply to the sprout pod. On Replicated installs, the
SDK injects global.replicated.dockerconfigjson and creates an
enterprise-pull-secret Secret in the release namespace; the
sprout.imagePullSecrets helper auto-detects that injection and merges the SDK
secret in front of anything listed here, so customers do NOT need to add
enterprise-pull-secret themselves. Use this list only for additional,
customer-supplied registries.
nameOverride
Type: string
Default: ""
Override the name of the chart
fullnameOverride
Type: string
Default: ""
Override the full name of the resources
serviceAccount.create
Type: bool
Default: true
Specifies whether a service account should be created. Sprout uses its ServiceAccount only as the identity behind the projected tokens it presents to the directory and budgets services — it never calls the Kubernetes API itself.
serviceAccount.automount
Type: bool
Default: false
Automatically mount a ServiceAccount's API-server credentials. Sprout never calls the Kubernetes API, so this stays false; the directory/budgets tokens below are separate, audience-bound PROJECTED volumes, not the automounted API-server token.
serviceAccount.annotations
Type: object
Default: {}
Annotations to add to the service account
serviceAccount.name
Type: string
Default: ""
The name of the service account to use (if not set and create is true, a name is generated using the fullname template)
podAnnotations
Type: object
Default: {}
Annotations to add to the pod
podLabels
Type: object
Default: {}
Labels to add to the pod
podSecurityContext
Type: object
Default value
fsGroup: 65532
runAsGroup: 65532
runAsNonRoot: true
runAsUser: 65532
seccompProfile:
type: RuntimeDefault
Security context for the pod (ko/Chainguard nonroot UID = 65532)
securityContext
Type: object
Default value
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532
Security context for the container
service.type
Type: string
Default: "ClusterIP"
Kubernetes service type
service.port
Type: int
Default: 8080
Service port
service.targetPort
Type: int
Default: 8080
Container target port. Matches -addr's default (":8080") in
cmd/sprout/main.go. The Service exposes ONLY this port — never the dedicated
/metrics listener (telemetry.metrics.port below), which serves on its own
listener specifically so it is never reachable through this customer-facing
Service.
telemetry
Type: object
Default value
headerLabels: []
metrics:
port: 9464
otlp:
enabled: false
endpoint: ''
insecure: false
prometheus:
enabled: false
tracing:
enabled: false
samplingRate: 0
Metrics and tracing export (opt-in). Both metrics exporters and tracing default
off — no OTLP push, no Prometheus pull, no /metrics route, no spans anywhere.
Field names mirror cmd/sprout/main.go's
telemetryConfig/otlpConfig/prometheusConfig/tracingConfig exactly
(Enabled/Insecure/Endpoint on otlp; Enabled on prometheus; Enabled/ SamplingRate
on tracing) — sprout's strict decoder rejects any field these structs don't
declare, so this chart must never invent one. otlp composes with
global.stacklok.telemetry.otlp (#4409) field by field: endpoint coalesces
(this chart's own value wins when set, else the global endpoint is used);
enabled/insecure are ADDITIVE with their global counterpart (true if either
is true) — there is no local setting that forces either off while the global
default is on. tracing has no global counterpart of its own; it shares
whichever otlp.endpoint (local-or-global) that composition resolves.
telemetry.otlp.enabled
Type: bool
Default: false
Push metrics to an OTLP collector.
telemetry.otlp.endpoint
Type: string
Default: ""
Collector endpoint (e.g. "otel-collector.observability:4318"). REQUIRED whenever otlp.enabled OR tracing.enabled is true (locally or, for otlp.enabled, via global.stacklok.telemetry.otlp.enabled) — the chart render fails when no endpoint is set, locally or via global.
telemetry.otlp.insecure
Type: bool
Default: false
Disable TLS on the OTLP connection. Only for a collector reachable over a private, trusted network.
telemetry.prometheus.enabled
Type: bool
Default: false
Expose a pull-scrapeable /metrics route on its own dedicated listener (metrics.port below).
telemetry.tracing
Type: object
Default: {"enabled":false,"samplingRate":0}
Push traces to the SAME OTLP collector as otlp.endpoint above — tracing and metrics are independent signals sharing one transport. Off by default.
telemetry.tracing.enabled
Type: bool
Default: false
Export spans via OTLP. Requires otlp.endpoint to be set (shared with metrics.otlp above), independently of otlp.enabled.
telemetry.tracing.samplingRate
Type: int
Default: 0
Root-span sampling ratio (0.0-1.0). A sampled inbound traceparent is always honored regardless of this ratio (parent-based sampling). Zero (the default) is treated as "sample every root span" — leaving it unset when enabled is true doesn't silently sample nothing.
telemetry.metrics
Type: object
Default: {"port":9464}
Dedicated /metrics listener, bound whenever prometheus.enabled is true. 9464 is OTel's conventional Prometheus-exporter port.
telemetry.headerLabels
Type: list
Default: []
Bounded header-to-metric-attribute mappings the token-usage metric carries in
addition to model/provider/token_type (telemetryConfig .HeaderLabels).
permitted is the CLOSED set of values that pass through verbatim; any other
observed value folds to "other", which is what keeps an operator-controlled
header from becoming an unbounded-cardinality label. Absent by default: no
header contributes a label.
Example:
headerLabels:
- header: x-stacklok-team
attribute: team
permitted: [platform, research]
resources
Type: object
Default:
{"limits":{"memory":"256Mi"},"requests":{"cpu":"50m","memory":"128Mi"}}
Resource limits and requests for the container. No CPU limit: an LLM gateway request holds its goroutine mostly blocked on upstream I/O, so a CPU ceiling only risks throttling a burst of concurrent streams without bounding anything the memory limit does not already bound.
readinessProbe
Type: object
Default value
failureThreshold: 2
httpGet:
path: /readyz
port: http
initialDelaySeconds: 0
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 3
Readiness probe. Hits /readyz, which answers 200 while serving and {"status":"not_ready","reason":"draining"} once a graceful drain has started.
livenessProbe
Type: object
Default value
failureThreshold: 32
httpGet:
path: /healthz
port: http
initialDelaySeconds: 0
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 5
Liveness probe. Hits /healthz, a static 200 that never rolls up a dependency.
Its failureThreshold/periodSeconds are sized against the pod-grace inequality:
(failureThreshold - 1) * periodSeconds > drain-deadline + completion-webhook budget
At the shipped lifecycle.maxStreamDuration (15m) ceiling, drain-deadline is 900s
and the completion-webhook budget is 5s, so the right-hand side is 905s.
(32 - 1) * 30 = 930s > 905s satisfies it. templates/_helpers.tpl's
sprout.validateLivenessBound guard recomputes this from whatever
lifecycle.maxStreamDuration and livenessProbe values an operator sets, and fails
the render naming both sides if the inequality breaks.
runtime
Type: object
Default: {"drainDeadline":"","drainWindow":"","jwksWarmupTimeout":""}
Process flags cmd/sprout reads at startup, which are NOT config-file fields (the binary takes them on argv). Each is optional; leaving one empty renders no flag and the binary keeps its own compiled-in default.
runtime.drainWindow
Type: string
Default: ""
-drain-window: how long a draining pod keeps ACCEPTING connections after
SIGTERM, buying time for its EndpointSlice removal to propagate through
kube-proxy/CNI. Go duration string. Binary default 5s.
runtime.drainDeadline
Type: string
Default: ""
-drain-deadline: overrides the deadline otherwise DERIVED from
lifecycle.maxStreamDuration (drainDeadlineFor in cmd/sprout/main.go). Leave
empty to keep the derived value, which is what the rendered
terminationGracePeriodSeconds is sized against — setting this without also
raising lifecycle.maxStreamDuration decouples the two. Go duration string.
runtime.jwksWarmupTimeout
Type: string
Default: ""
-jwks-warmup-timeout: per-issuer budget for the blocking first JWKS fetch at
startup. An issuer that misses it is fatal (the process exits rather than
serving 401s with a valid token). Go duration string. Binary default 10s.
lifecycle.maxStreamDuration
Type: string
Default: "15m"
The upstream stream ceiling every other timing in this chart derives from: the derived terminationGracePeriodSeconds, the liveness-bound guard above, the ConfigMap's lifecycle.max_stream_duration (which the binary's own validateDrainBudgetFitsGrace re-checks at startup), and httpRoute.timeouts below. Go duration string.
providers
Type: list
Default: []
Upstream providers. Each entry mirrors catalog.ProviderConfig (enterprise/sprout/catalog/catalog.go): kind, baseUrl, models, and the per-kind fields a kind actually reads (region for bedrock-anthropic/vertex; project for vertex; awsProfile for bedrock-anthropic). credentialSecretRef names the Secret+key backing that provider's credential; the chart mounts it and writes the matching __file field (api_key_file / aws_credentials_file / gcp_service_account_file) — never an inline plaintext credential, which templates/_helpers.tpl's sprout.validateConfig REFUSES outright (the ConfigMap is plaintext; only the __file indirection keeps the credential bytes in a Secret). A provider naming no credentialSecretRef renders no mount and no *_file key — the shipped Bedrock ambient-credential-chain posture (EKS Pod Identity) keeps working.
Example:
providers:
- kind: openai
baseUrl: https://api.openai.com
models: ['gpt-5.5']
credentialSecretRef:
name: ai-gateway-v2-openai-credentials
key: api-key
- kind: bedrock-anthropic
baseUrl: https://bedrock-runtime.us-east-1.amazonaws.com
region: us-east-1
models: ['us.anthropic.claude-opus-4-8']
# No credentialSecretRef: resolves the AWS default credential chain
# (EKS Pod Identity) at runtime.
auth
Type: object
Default: {"issuers":[]}
Trusted OIDC issuers for the JWT authentication path (authConfig.Issuers in cmd/sprout/main.go): issuer/audience/bindingClaim/tokenUse/jwksUri.
directory.endpoint
Type: string
Default: ""
Directory gRPC endpoint (host:port), e.g. "enterprise-server.toolhive-system:9091". Empty disables virtual-key resolution and group-label telemetry.
directory.insecure
Type: bool
Default: false
Local development only: dial the directory over plaintext with no caller credential projected.
directory.groupsResolutionIssuer
Type: string
Default: ""
The iss the virtual-key path presents to ResolveIdentity so the directory can
bind the resolved uid to an issuer and return its groups.
directory.groupLabels
Type: list
Default: []
Operator-chosen allowlist of directory GroupRef.Labels names (e.g. department, cost_center; see #3895 for how a group acquires a label) this gateway resolves onto Principal.Labels, their own governance header (X-<Label>, e.g. "department" -> X-Department, "cost_center" -> X-Cost-Center), and the audit trail. Empty by default: no label is resolved or forwarded. Each entry must be a bounded lower-case underscore-separated identifier and must not derive a header colliding with a reserved governance header or a telemetry.headerLabels mapping — the gateway refuses to start otherwise.
directory.tokenProjection.enabled
Type: bool
Default: false
Project a ServiceAccount token for directory.token_file, mounted at <mountPath>/token. Required whenever directory.endpoint is set and directory.insecure is false, or the gateway has no caller credential to present and the directory's caller gate refuses it.
directory.tokenProjection.mountPath
Type: string
Default: "/var/run/secrets/directory"
Directory the projected token is mounted into.
directory.tokenProjection.audience
Type: string
Default: "directory-service"
Audience the directory's caller-auth gate expects.
directory.tokenProjection.expirationSeconds
Type: int
Default: 3600
Requested lifetime of the projected token. Minimum 600 — the API server REJECTS a Pod spec below that floor rather than clamping to it.
budgets.endpoint
Type: string
Default: ""
Budget webhook receiver base URL, e.g. "https://enterprise-server.toolhive-system:443". Empty leaves the gateway UNGOVERNED (no admission, no charge) — see acknowledgeUnmetered below.
budgets.caBundle.secretName
Type: string
Default: ""
Secret holding the PEM bundle that verifies the budgets receiver's serving certificate. Required when endpoint is set: falls back to global.webhooks.caBundleSecret, and the render fails when neither names a Secret. Paired with tokenProjection below.
budgets.caBundle.key
Type: string
Default: "ca.crt"
Key within that Secret holding the PEM bundle.
budgets.mountPath
Type: string
Default: "/etc/budgets-tls"
Directory the CA bundle is mounted into. Each rendered budgets hook entry's ca_file is <mountPath>/<caBundle.key>.
budgets.tokenProjection.enabled
Type: bool
Default: false
Project a ServiceAccount token for the budgets hook entries' token_file, validated by the receiver as an audience-bound Bearer. Required when endpoint is set.
budgets.tokenProjection.mountPath
Type: string
Default: "/var/run/secrets/budgets"
Directory where the projected budgets ServiceAccount token is mounted. Budgets hooks read the token file beneath this path.
budgets.tokenProjection.audience
Type: string
Default: "budgets-webhook"
Audience requested for the projected budgets ServiceAccount token. Match the audience accepted by Enterprise Manager's budgets receiver.
budgets.tokenProjection.expirationSeconds
Type: int
Default: 3600
Requested lifetime, in seconds, of the projected budgets ServiceAccount token. Kubernetes rotates the token before expiry.
budgets.capture
Type: bool
Default: false
Opt this gateway into conversation capture (forwards the client request body to the receiver). Default false. Capture fires after the required hooks, so with scanning on it carries the redacted body. With scanning off it carries the raw body, and the install notes warn about it.
budgets.sessionHeaders
Type: list
Default: []
Ordered session-correlation candidate headers this gateway projects. INHERITED
from the umbrella's global.stacklok.sessionHeaders when left empty, so one
setting covers whichever data plane is installed and the gateway cannot drift
from the receiver that keys on it. Set here only for the rare install whose
gateway must forward a different list than its receiver keys on. An empty list
at BOTH levels renders the shipped defaults (x-claude-code-session-id,
x-mecatl-root-session-id, x-mecatl-session-id, session-id) onto each budgets
hook entry.
budgets.acknowledgeUnmetered
Type: bool
Default: false
REQUIRED (true) to render a chart with no budgets.endpoint configured. The unified webhook lifecycle makes the budgets receiver's 200 the definition of "tracked" — a gateway with no budgets endpoint is unmetered, and templates/_helpers.tpl's sprout.validateConfig refuses that render unless an operator explicitly acknowledges it here, so "no budgets configured" can never be an accident.
modelAccess.enabled
Type: bool
Default: false
Enforce directory model access on this gateway. Inference is refused (HTTP 403, before any provider call) for a model the caller's grants do not cover, and /v1/models lists only the models the caller may use. Both halves fail closed. Needs directory.endpoint (the discovery filter dials the directory's ResolveModelAccess RPC). Reuses budgets.tokenProjection (its audience must equal the receiver's budgets audience) and budgets.caBundle / global.webhooks.caBundleSecret for the receiver's token and CA, so it also works on a gateway that is deliberately unmetered.
modelAccess.endpoint
Type: string
Default: ""
Receiver base URL, e.g. "https://enterprise-server.toolhive-system:443". Defaults to budgets.endpoint, which is the same listener.
audit.enabled
Type: bool
Default: false
Enable audit logging (auditConfig.Enabled). ADDITIVE with global.stacklok.audit.enabled (#4409, the umbrella's fleet-wide audit default): audit is on if EITHER this is true OR the global default is. There is no way to force this OFF while the global default is on.
capture
Type: object
Default: {}
Inline capture config passthrough (sprout.CaptureConfig), rendered verbatim into the ConfigMap's "capture" key when non-empty. ABSENT BY DEFAULT and never enabled implicitly: capture snapshots record full request and response bodies.
networkPolicy.enabled
Type: bool
Default: false
Enable a NetworkPolicy for the sprout pod. Default off: regulated customers running a default-deny CNI policy engine opt in explicitly.
networkPolicy.ingress.gatewayNamespaces
Type: list
Default: ["envoy-gateway-system"]
Namespaces whose pods may reach the gateway port. The shared Envoy Gateway's data plane is the only thing that should: sprout sits behind it and has no ingress of its own. An EMPTY list with allowAllSources false FAILS the render rather than silently admitting every pod in the cluster, because a NetworkPolicy that looks locked down but is not is worse than none at all.
networkPolicy.ingress.gatewayPodSelector
Type: object
Default: {"app.kubernetes.io/name":"envoy"}
Optional narrowing WITHIN those namespaces. The default names the Envoy pods
owning the shared eg Gateway, so the Envoy Gateway control plane and the
rate-limit pod beside them are not admitted. Set to {} to admit the
whole namespace.
networkPolicy.ingress.monitoringNamespaces
Type: list
Default: ["monitoring"]
Namespaces whose pods may scrape the dedicated /metrics listener, applied only when telemetry.prometheus.enabled is true. Scoped to the metrics port alone, so admitting a monitoring namespace never becomes a second way into the gateway's ingress listener. The listener is UNAUTHENTICATED, so this list is the only thing bounding who reads it.
networkPolicy.ingress.allowAllSources
Type: bool
Default: false
Admit every source instead of the lists above. An explicit, auditable opt-out for an install whose namespace topology the defaults do not fit; it is not a default, because an allow-all ingress rule leaves the unauthenticated /metrics listener readable by every pod in the cluster.
networkPolicy.governanceEgress
Type: object
Default:
{"enabled":true,"namespaces":[],"podSelector":{"app.kubernetes.io/name":"enterprise-manager"}}
When allowExternalEgress is false, emit egress rules to the governance services
this install actually dials (directory, budgets, and the enterprise-manager the
Gateway connectivity poll reads), with the ports taken from directory.endpoint
and budgets.endpoint, and enterprise-manager admitted by its named http
container port, so a port change cannot orphan the rule. Without them a
locked-down install fails CLOSED at request time — budget admission is
fail-closed, so every request 503s — with nothing in the Helm or pod state to
point at.
networkPolicy.governanceEgress.namespaces
Type: list
Default: []
Namespaces the governance services run in. Empty means the release namespace, which is where the umbrella installs enterprise-manager. If directory.endpoint or budgets.endpoint instead point at a service in a DIFFERENT namespace, this must be set to match, or the emitted egress rule scopes to the wrong namespace and admits nothing — the same silent fail-closed 503 as a port mismatch, since NetworkPolicy has no visibility into what a hostname resolves to, only which namespace/pod selector it's scoped to.
networkPolicy.governanceEgress.podSelector
Type: object
Default: {"app.kubernetes.io/name":"enterprise-manager"}
Pods within those namespaces. Set to {} to admit the whole namespace.
networkPolicy.allowExternalEgress
Type: bool
Default: true
Allow ALL egress traffic. NOTE: this GRANTS allow-all egress, overriding any
cluster-wide default-deny egress policy. Set to false and populate egress
below to actually lock this pod down.
networkPolicy.egress
Type: list
Default: []
Additional egress rules (Kubernetes NetworkPolicyEgressRule shape), applied when
allowExternalEgress is false. Must cover DNS (UDP/TCP 53) plus every
provider/directory/budgets/OTLP destination this install actually dials. When a
configured bedrock-anthropic provider carries no static credentials and no
aws_credentials_file (the ambient EKS-Pod-Identity chain), the chart
automatically adds the Pod Identity Agent's link-local egress rule
(169.254.170.23/32 and fd00:ec2::23/128, TCP 80) — no need to list it here.
extraEnv
Type: list
Default: []
Additional environment variables for the sprout container.
extraVolumes
Type: list
Default: []
Additional volumes to attach to the pod.
extraVolumeMounts
Type: list
Default: []
Additional volume mounts to attach to the sprout container.
nodeSelector
Type: object
Default: {}
Node labels for pod assignment
tolerations
Type: list
Default: []
Tolerations for pod assignment
affinity
Type: object
Default: {}
Affinity rules for pod assignment
topologySpreadConstraints
Type: list
Default:
[{"maxSkew":1,"topologyKey":"kubernetes.io/hostname","whenUnsatisfiable":"ScheduleAnyway"}]
Keep the replicas off one node, so a single node loss cannot take the whole data
plane with it. ScheduleAnyway, not DoNotSchedule: a pod that cannot spread
must still run, since refusing to schedule would trade a redundancy goal for an
outage. Set to [] to disable spreading entirely.
httpRoute.enabled
Type: bool
Default: false
Render a Gateway API HTTPRoute attaching this Service to the shared Envoy
Gateway. Default off: the chart cannot guess the shared Gateway's
name/namespace, so a bare helm install would otherwise fail the render on an
empty parentRefs. Set true and populate parentRefs below once you know the
target Gateway.
httpRoute.parentRefs
Type: list
Default: []
parentRefs (Gateway API HTTPRouteSpec.ParentRefs shape): name, namespace, and optional sectionName of the shared Gateway to attach to. Required when httpRoute.enabled is true.
httpRoute.hostname
Type: string
Default: ""
Hostname this route serves. Optional; omitted from the rendered route when empty (matches every hostname the parent Gateway listener accepts).
config.mountPath
Type: string
Default: "/etc/ai-gateway-v2/config"
Directory the config ConfigMap is mounted into.
config.fileName
Type: string
Default: "config.json"
File name of the rendered config JSON within mountPath. Sprout takes ONE
-config flag naming this single file (unlike connector-gateway's two-file
OSS/enterprise split) — the full path is passed via the -config container arg.
config.credentialsRoot
Type: string
Default: "/var/run/secrets/ai-gateway-v2"
Directory every provider credential file (api_key_file, aws_credentials_file,
gcp_service_account_file) must resolve inside. Passed via the
-credentials-root container arg — a FLAG, not a config key, so the ConfigMap's
writer cannot widen it (cmd/sprout/main.go's readCredentialUnderRoot). Matches
defaultCredentialsRoot.
scanning
Type: object
Default value
adapter:
image:
digest: ''
pullPolicy: IfNotPresent
repository: image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/ai-gateway-main-processor
tag: ''
logLevel: ''
maxReplicas: null
metrics:
enabled: false
port: 9090
nodeSelector: {}
podAnnotations: {}
replicas: 2
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 50m
memory: 64Mi
targetCPUUtilization: null
tolerations: []
topologySpreadConstraints: []
allowlist: []
allowlistMatch: exact
audience: ''
enabled: false
entities: []
entityActions: []
hook:
caMountPath: /etc/scanning-tls
timeout: ''
tokenExpirationSeconds: 3600
tokenMountPath: /var/run/secrets/scanning
ner:
circuitBreaker:
failureThreshold: 5
resetTimeout: 30s
concurrency: 8
failureAction: fail-closed
timeout: 5s
presidio:
autoscaling:
mode: HPA
image:
digest: ''
pullPolicy: IfNotPresent
repository: image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/presidio-analyzer
tag: ''
language: en
maxReplicas: null
nodeSelector: {}
podAnnotations: {}
recognizers: []
replicas: 2
resources:
limits:
cpu: '2'
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
scoreThreshold: 50
targetCPUUtilization: null
tolerations: []
topologySpreadConstraints: []
requestAction: block
resultCache:
enabled: false
macSecret:
key: mac-secret
name: ''
redis:
clusterMode: null
existingSecret: ''
existingSecretKey: ''
host: ''
port: null
tls: null
username: ''
ttl: ''
tls:
caBundleKey: ca.crt
caBundleSecret: ''
issuerRef:
kind: ''
name: ''
PII scanning. Enabling it renders the Presidio Analyzer stack and a
scanning-adapter (the main-processor image in scanning-adapter mode) that
turns Presidio detections into an allow/redact/block verdict, and wires the
gateway's scan hook to that adapter. The gateway's scanning config block is
derived from these values: endpoint, CA file, projected token and timeout all
follow the adapter the chart renders, so scanning.enabled=true plus an issuer
and a CA bundle is enough for a working scan.
scanning.enabled
Type: bool
Default: false
Render the Presidio stack and the scanning-adapter (each with its Deployment, Service, ServiceAccount and NetworkPolicy, plus the adapter's ClusterRoleBinding, serving Certificate and config) and wire the gateway's scan hook to the adapter. Off by default; when false nothing scanning-related renders, including the cluster-scoped ClusterRoleBinding.
scanning.audience
Type: string
Default: ""
Audience of the token the gateway presents to the adapter. The adapter requires
exactly this audience (401 otherwise). Empty derives
https://<adapter-service>.<release-namespace>.svc.
scanning.requestAction
Type: string
Default: "block"
Global action when Presidio finds an entity: block (403), redact (replace
the match with a placeholder) or log-only.
scanning.entityActions
Type: list
Default: []
Per-entity overrides of requestAction. Each entry: entity (Presidio entity
name), action (block|redact|log-only) and an optional placeholder used
when the action is redact.
scanning.entities
Type: list
Default: []
Presidio entities to detect. Empty means the default list in files/presidio/default_entities.json (PERSON, LOCATION, DATE_TIME, CREDIT_CARD, US_SSN, PHONE_NUMBER, EMAIL_ADDRESS, IBAN_CODE, IP_ADDRESS, US_ITIN, UK_NHS). The supportedEntity of every custom recognizer under presidio.recognizers is always added. Editing this list (or adding a recognizer with a new entity) rolls the adapter, so an invalid entity fails the rollout instead of being ignored. That includes a built-in whose recognizer the bundled Presidio registry ships disabled (most country-specific ones, e.g. IN_AADHAAR, AU_TFN), which Presidio would otherwise skip without detecting anything.
scanning.allowlist
Type: list
Default: []
Strings Presidio never reports as a detection.
scanning.allowlistMatch
Type: string
Default: "exact"
How allowlist entries match: exact or regex.
scanning.ner.timeout
Type: string
Default: "5s"
Budget for one Presidio call, as a single-unit Go duration. Must be positive.
scanning.ner.concurrency
Type: int
Default: 8
Concurrent Presidio calls within one request. The adapter scans a request's text nodes in waves of this size. It is not a pod-wide cap: every in-flight request gets its own limit.
scanning.ner.failureAction
Type: string
Default: "fail-closed"
What the adapter does when Presidio is unreachable or slow: fail-closed blocks
the request, fail-open admits it.
scanning.ner.circuitBreaker.failureThreshold
Type: int
Default: 5
Consecutive Presidio failures that trip the breaker.
scanning.ner.circuitBreaker.resetTimeout
Type: string
Default: "30s"
How long the breaker stays open before probing again.
scanning.hook.timeout
Type: string
Default: ""
Budget the gateway gives the adapter, as a single-unit Go duration. Empty derives ner.timeout + 2s, so a slow Presidio surfaces the adapter's fail-closed 403 rather than the gateway's own 504. That covers one wave of Presidio calls. The adapter has no request-wide deadline, so a request with more than ner.concurrency text nodes runs several waves, and a slow Presidio that still answers within ner.timeout can push it past this budget into a gateway 504. Raise it if large requests need more than one wave under load. An explicit value must exceed ner.timeout.
scanning.hook.tokenExpirationSeconds
Type: int
Default: 3600
Requested lifetime of the projected token, minimum 600.
scanning.hook.tokenMountPath
Type: string
Default: "/var/run/secrets/scanning"
Directory the projected token is mounted into on the gateway pod.
scanning.hook.caMountPath
Type: string
Default: "/etc/scanning-tls"
Directory the adapter CA bundle is mounted into on the gateway pod.
scanning.tls.issuerRef
Type: object
Default: {"kind":"","name":""}
cert-manager issuer for the adapter's serving certificate. Empty name falls back to global.webhooks.issuerRef; one of the two must name an issuer.
scanning.tls.caBundleSecret
Type: string
Default: ""
Secret holding the PEM CA that verifies the adapter's serving certificate, in the release namespace. Empty falls back to global.webhooks.caBundleSecret; one of the two is required.
scanning.tls.caBundleKey
Type: string
Default: "ca.crt"
Key within that Secret holding the PEM bundle.
scanning.adapter.image.repository
Type: string
Default value
image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/ai-gateway-main-processor
Scanning-adapter image: the ai-gateway main-processor, run with PROCESSOR_MODE=scanning-adapter.
scanning.adapter.image.tag
Type: string
Default: ""
Image tag (defaults to appVersion if empty).
scanning.adapter.image.digest
Type: string
Default: ""
Optional image digest, appended as @<digest> after the tag.
scanning.adapter.image.pullPolicy
Type: string
Default: "IfNotPresent"
Image pull policy.
scanning.adapter.replicas
Type: int
Default: 2
Replica count. Also the HPA's minReplicas when one renders. A PodDisruptionBudget (maxUnavailable 1) renders at 2 or more, and the pods prefer separate nodes. The scan hook is fail-closed, so a single adapter is a single point of failure for every request.
scanning.adapter.maxReplicas
Type: string
Default: nil
Upper bound for the HPA. Unset means no HPA, and replicas is written on the
Deployment.
scanning.adapter.targetCPUUtilization
Type: string
Default: nil
HPA target average CPU utilization, 1-100. Requires maxReplicas above 1, the threshold at which an HPA renders. Defaults to 75 when unset.
scanning.adapter.podAnnotations
Type: object
Default: {}
Annotations added to the adapter pods. The chart's own checksum and scrape annotations win over a same-named entry.
scanning.adapter.nodeSelector
Type: object
Default: {}
Node labels for adapter pod assignment. The scan hook is fail-closed, so set this (and tolerations) to follow the gateway onto dedicated nodes.
scanning.adapter.tolerations
Type: list
Default: []
Tolerations for adapter pod assignment.
scanning.adapter.topologySpreadConstraints
Type: list
Default: []
Topology spread constraints for the adapter pods. The chart injects the adapter's selector labels into each entry that sets no labelSelector.
scanning.adapter.resources
Type: object
Default:
{"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}
Adapter container resources.
scanning.adapter.logLevel
Type: string
Default: ""
Adapter log level (debug|info|warn|error). Empty keeps the binary default.
scanning.adapter.metrics.enabled
Type: bool
Default: false
Serve the adapter's Prometheus metrics on port, and admit scrapes to that port
in its NetworkPolicy from networkPolicy.ingress.monitoringNamespaces (or from
any source under networkPolicy.ingress.allowAllSources).
scanning.resultCache.enabled
Type: bool
Default: false
Cache Presidio results in Redis/Valkey, keyed by request content and the
detection inputs (recognizers checksum, Presidio image, PSL snapshot). While the
Presidio Deployment is mid-rollout, old and new pods answer side by side, so the
adapter withholds cache writes until every Presidio pod runs the detection
inputs its key encodes and that state has held for 30s. Reads continue
throughout. The adapter reads the Deployment through a namespaced Role the chart
grants; if it cannot, the cache stays read-only. Withheld writes are counted on
stacklok_ai_gateway_ner_cache_withheld_writes_total, so a rate that never drops
means the cache has gone read-only. This release's entries live under
aigw:ner:v1:<release-namespace>:<fullname>:. Requires a Redis host here or in
global.redis.
scanning.resultCache.ttl
Type: string
Default: ""
Entry lifetime as a Go duration. Empty keeps the runtime default (24h).
scanning.resultCache.redis.host
Type: string
Default: ""
Redis host. Empty falls back to global.redis.host.
scanning.resultCache.redis.port
Type: string
Default: nil
Redis port. Empty falls back to global.redis.port.
scanning.resultCache.redis.clusterMode
Type: string
Default: nil
Speak the Redis Cluster protocol. Unset falls back to global.redis.clusterMode.
scanning.resultCache.redis.username
Type: string
Default: ""
ACL username. Empty falls back to global.redis.username.
scanning.resultCache.redis.existingSecret
Type: string
Default: ""
Secret holding the Redis password, in the release namespace. Empty falls back to global.redis.existingSecret.
scanning.resultCache.redis.existingSecretKey
Type: string
Default: ""
Key within that Secret. Empty falls back to global.redis.existingSecretKey.
scanning.resultCache.redis.tls
Type: string
Default: nil
Dial Redis over TLS. Unset falls back to global.redis.tls.enabled.
scanning.resultCache.macSecret.name
Type: string
Default: ""
Bring your own Secret holding the cache's HMAC key, in the release namespace (e.g. one managed by External Secrets or sealed-secrets). The adapter then only reads it and creates nothing, which is what makes the arrangement safe: a second writer at the same name would race the manager that owns it and replace the value, cold-starting the cache.
Empty makes the adapter provision one itself, named <fullname>-ner-cache-mac:
at startup it gets the Secret and creates it when absent, and replicas that race
on the create converge on one key. Provisioning is create-if-absent on every
start, so it works on a fresh install and an upgrade alike, and a restart never
replaces a live value. The value is 48 bytes of crypto/rand, base64-encoded so
it is safe to carry as an env var, and it never passes through Helm values. The
chart renders a Role and RoleBinding granting the adapter get (pinned to that
Secret) and create in the release namespace; without that grant the adapter logs
a WARN and runs uncached. The Secret is not owned by the release, so it survives
uninstall and a Helm or Argo prune.
A supplied Secret's value must be at least 32 bytes of printable text, not raw bytes: it reaches the pod as an env var, and the kubelet cannot ship a non-UTF-8 env value over CRI (the pod sticks in CreateContainerError). A missing Secret leaves the cache off.
Rotation, either way, is to replace the Secret's value and restart the adapter. Every existing entry then fails verification and is re-scanned: a cache cold start, never a correctness event, and the whole incident response for a leaked secret.
scanning.resultCache.macSecret.key
Type: string
Default: "mac-secret"
Key within that Secret. Applies to a supplied Secret and to the one the adapter provisions.
scanning.presidio.image.repository
Type: string
Default value
image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/presidio-analyzer
Presidio Analyzer image, built from the pinned upstream 2.2.364 source and published at the platform tag.
scanning.presidio.image.tag
Type: string
Default: ""
Image tag (defaults to appVersion if empty).
scanning.presidio.image.digest
Type: string
Default: ""
Optional image digest, appended as @<digest> after the tag.
scanning.presidio.image.pullPolicy
Type: string
Default: "IfNotPresent"
Image pull policy.
scanning.presidio.replicas
Type: int
Default: 2
Replica count. Also the HPA's minReplicas when one renders. A PodDisruptionBudget renders at 2 or more.
scanning.presidio.maxReplicas
Type: string
Default: nil
Upper bound for the HPA. Above 1, with autoscaling.mode HPA, the chart renders
an HPA and omits replicas from the Deployment. Unset means no HPA.
scanning.presidio.targetCPUUtilization
Type: string
Default: nil
HPA target average CPU utilization, 1-100. Requires an HPA to render (autoscaling.mode HPA and maxReplicas above 1). Defaults to 75 when unset.
scanning.presidio.autoscaling.mode
Type: string
Default: "HPA"
Who owns the replica count. HPA: the chart renders an HPA when maxReplicas
> 1. External: an out-of-band scaler (e.g. KEDA) owns it, so the chart
renders no HPA and omits replicas. None: the chart pins replicas and
renders no HPA. On a live install, the upgrade that first omits replicas
(setting maxReplicas, or switching to External) makes Helm's three-way merge
remove the field, so the Deployment drops to 1 analyzer until the scaler acts,
and that pod must pass the warmup startupProbe before it serves. Make that
switch in a low-traffic window.
scanning.presidio.podAnnotations
Type: object
Default: {}
Annotations added to the Presidio pods. The chart's own checksum annotations win over a same-named entry.
scanning.presidio.nodeSelector
Type: object
Default: {}
Node labels for Presidio pod assignment.
scanning.presidio.tolerations
Type: list
Default: []
Tolerations for Presidio pod assignment.
scanning.presidio.topologySpreadConstraints
Type: list
Default: []
Topology spread constraints for the Presidio pods. The chart injects the Presidio selector labels into each entry that sets no labelSelector.
scanning.presidio.resources
Type: object
Default:
{"limits":{"cpu":"2","memory":"2Gi"},"requests":{"cpu":"500m","memory":"1Gi"}}
Analyzer container resources.
scanning.presidio.language
Type: string
Default: "en"
Analysis language, stamped onto every custom recognizer and used by the startup
warmup request. Only en is supported.
scanning.presidio.scoreThreshold
Type: int
Default: 50
Minimum Presidio confidence, 0-100, for a detection to count. Read by the scanning-adapter; it does not change the Presidio pods. 0 is honoured and counts every detection.
scanning.presidio.recognizers
Type: list
Default: []
Custom Pattern recognizers, appended after the vendored default recognizers.
Each entry: name (unique across the custom and vendored default recognizers),
supportedEntity (an UPPER_SNAKE entity name that is not one of Presidio's
built-in entities), and at least one of patterns (name unique within the
recognizer, regex in RE2 syntax, score 0-100, default 50) or denyList;
optional contextWords. A regex that does not compile, or a recognizers
ConfigMap over 1 MiB, fails the render. Editing this list rolls the Presidio
pods.
guardrails.enabled
Type: bool
Default: false
Render the guardrails-adapter (Deployment, Service, ServiceAccount,
NetworkPolicy, plus its ClusterRoleBinding and serving Certificate) and wire the
gateway's guardrails hook to it. The adapter screens each request's
user-authored text with Amazon Bedrock Guardrails. Off by default; when false
nothing guardrails-related renders, including the cluster-scoped
ClusterRoleBinding. The hook runs after scanning, so it screens the redacted
body.
guardrails.mode
Type: string
Default: "Enforce"
Enforce blocks a request the guardrail intervenes on. Monitor screens off
the request path and never blocks: the hook is an observation entry, so the
request is forwarded without waiting for the verdict. A screen Enforce would
have blocked is counted on stacklok.ai_gateway.webhook.invocations
(webhook_name="guardrails", webhook_outcome="deny") and logged at WARN with
the guardrail's annotations. The counts are best-effort: a fire shed under queue
pressure is counted on stacklok.ai_gateway.webhook.invocations_dropped
instead.
guardrails.failurePolicy
Type: string
Default: "Fail"
What happens when the adapter is unreachable, slow or errors, under Enforce
only: Fail blocks the request, Ignore admits it. Monitor never blocks.
guardrails.unscreenableContentPolicy
Type: string
Default: "Deny"
What happens to a request whose user content carries no screenable text (media
only, or an unrecognized shape): Deny blocks it, Admit lets it through
unscreened.
guardrails.audience
Type: string
Default: ""
Audience of the token the gateway presents to the adapter. The adapter requires
exactly this audience (401 otherwise). Empty derives
https://<adapter-service>.<release-namespace>.svc.
guardrails.bedrock.guardrailId
Type: string
Default: ""
Bedrock guardrail identifier. Required.
guardrails.bedrock.guardrailVersion
Type: string
Default: ""
Guardrail version: DRAFT or a positive integer, written as a string ("1",
not 1). Required.
guardrails.bedrock.region
Type: string
Default: ""
AWS region of the guardrail, e.g. us-east-1. Required.
guardrails.bedrock.promptWhitelist
Type: object
Default: {"prompts":[],"regexes":[]}
Prompts excluded from screening. prompts are exact matches on a complete user
turn. regexes are RE2 expressions that match anywhere in the turn unless
anchored, so anchor both ends (^ping$, not ^ping).
guardrails.bedrock.credentialsSecretRef
Type: object
Default: {"name":""}
Secret holding a shared AWS credentials file under the key credentials,
mounted into the adapter. Empty uses the pod's ambient AWS identity: EKS Pod
Identity (associate the role with the adapter's ServiceAccount,
<fullname>-guardrails-adapter) or IRSA (annotate it through
adapter.serviceAccount.annotations).
guardrails.bedrock.endpoint
Type: string
Default: ""
Bedrock Runtime base URL override, for a PrivateLink endpoint or a test double. Empty uses the SDK's regional endpoint.
guardrails.hook.timeout
Type: string
Default: "5s"
Budget the gateway gives the adapter, as a single-unit Go duration of at most 120s. The adapter's own Bedrock call must finish inside it.
guardrails.hook.tokenExpirationSeconds
Type: int
Default: 3600
Requested lifetime of the projected token, minimum 600.
guardrails.hook.tokenMountPath
Type: string
Default: "/var/run/secrets/guardrails"
Directory the projected token is mounted into on the gateway pod.
guardrails.hook.caMountPath
Type: string
Default: "/etc/guardrails-tls"
Directory the adapter CA bundle is mounted into on the gateway pod.
guardrails.tls.issuerRef
Type: object
Default: {"kind":"","name":""}
cert-manager issuer for the adapter's serving certificate. Empty name falls back to global.webhooks.issuerRef; one of the two must name an issuer.
guardrails.tls.caBundleSecret
Type: string
Default: ""
Secret holding the PEM CA that verifies the adapter's serving certificate, in the release namespace. Empty falls back to global.webhooks.caBundleSecret; one of the two is required.
guardrails.tls.caBundleKey
Type: string
Default: "ca.crt"
Key within that Secret holding the PEM bundle.
guardrails.adapter.image.repository
Type: string
Default value
image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/ai-gateway-main-processor
Guardrails-adapter image: the ai-gateway main-processor, run with PROCESSOR_MODE=guardrails-adapter.
guardrails.adapter.image.tag
Type: string
Default: ""
Image tag (defaults to appVersion if empty).
guardrails.adapter.image.digest
Type: string
Default: ""
Optional image digest, appended as @<digest> after the tag.
guardrails.adapter.image.pullPolicy
Type: string
Default: "IfNotPresent"
Image pull policy.
guardrails.adapter.replicas
Type: int
Default: 2
Replica count. Also the HPA's minReplicas when one renders. A
PodDisruptionBudget (maxUnavailable 1) renders at 2 or more, and the pods prefer
separate nodes. Under failurePolicy: Fail a single adapter is a single point
of failure for every request.
guardrails.adapter.maxReplicas
Type: string
Default: nil
Upper bound for the HPA. Unset means no HPA, and replicas is written on the
Deployment.
guardrails.adapter.targetCPUUtilization
Type: string
Default: nil
HPA target average CPU utilization, 1-100. Requires maxReplicas above 1, the threshold at which an HPA renders. Defaults to 75 when unset.
guardrails.adapter.podAnnotations
Type: object
Default: {}
Annotations added to the adapter pods. The chart's own checksum annotation wins over a same-named entry.
guardrails.adapter.nodeSelector
Type: object
Default: {}
Node labels for adapter pod assignment.
guardrails.adapter.tolerations
Type: list
Default: []
Tolerations for adapter pod assignment.
guardrails.adapter.topologySpreadConstraints
Type: list
Default: []
Topology spread constraints for the adapter pods. The chart injects the adapter's selector labels into each entry that sets no labelSelector.
guardrails.adapter.serviceAccount.annotations
Type: object
Default: {}
Annotations on the adapter's ServiceAccount, e.g. the
eks.amazonaws.com/role-arn of an IRSA role.
guardrails.adapter.resources
Type: object
Default:
{"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}
Adapter container resources.