Helm chart values¶
The kritika chart is documented value by value in the generated
chart README,
kept in step with values.yaml by helm-docs (CI fails if it goes stale). The
chart also ships a
values.schema.json
for editor completion and helm install validation. The chart README also
covers the egress gateway, runner tools and the runner sandbox; the database
has its own page, Postgres with CloudNativePG.
This page is the orientation: which groups of values exist and where their
behavior is explained, followed by the full values.yaml.
image,replicas,strategy,resourcesand the pod settings: the one Deployment ofkritika serve.web: the public URL the dashboard and GitHub's webhooks share.auth: how people sign in to the dashboard, as a local admin, through OIDC or with GitHub. See Configuration file.config: the configuration file, inline or from an existing ConfigMap, and how kritika runs (polling, retention, log level, workers). See Configuration file.databaseandsecretEnv: the owner, application and runner DSNs, and the Secrets that set the environment variables the configuration file names.runner: the image, TTL, deadline, resources, tools and RuntimeClass of the runner Jobs.gateway: the port of the egress gateway runner pods reach the outside and their model through.service,ingressandhttpRoute: the public and metrics ports and how the public one is exposed.networkPolicy: the policies that confine runner pods to the gateway.monitoringand the probes: see Metrics.
values.yaml¶
---
# Default values for kritika.
#
# Most string, list and map values below are rendered through Helm's `tpl`, so
# they may carry template expressions that resolve at install time, e.g.
# ingress.hosts[0].host: kritika.{{ .Values.global.domain }}
# Plain numbers and booleans, and `config.file`, are passed through as they are.
image:
# -- Image repository.
repository: ghcr.io/home-operations/kritika
# -- Image pull policy.
pullPolicy: IfNotPresent
# -- Overrides the image tag; defaults to the chart appVersion.
tag: ""
# -- Pin the image by digest (sha256:…); when set, overrides the tag. The release pipeline fills it with the published image's digest.
digest: ""
# -- Image pull secrets for private registries, for the kritika serve pods and, through the runner ServiceAccount, runner Jobs and the tool images they mount.
imagePullSecrets: []
# -- Override the chart name used in resource names.
nameOverride: ""
# -- Override the full release name.
fullnameOverride: ""
# kritika runs as one Deployment of `kritika serve`: webhooks, the dashboard,
# the job queues, runner Jobs, the gateway and, on the replica holding the
# leader lock, the leader duties. Runner pods are not part of it: serve
# creates them as Jobs, one per review and index run.
# -- Replicas of kritika serve. Every replica serves webhooks and the dashboard and works jobs; exactly one holds the leader lock at a time. Two keep one serving while a rollout replaces the other.
replicas: 2
# -- Deployment update strategy. A rolling update that surges one pod and takes none down keeps one replica serving while the other is replaced. Helm merges maps, so a switch to `Recreate` also sets `rollingUpdate: null`.
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
# The one public URL: the dashboard at `web.url`, and the webhooks under it
# at `/hooks`, both served on `service.port`, which the Ingress or HTTPRoute
# below route to.
web:
# -- (required) Public URL the dashboard is reached at, e.g. https://kritika.example.com; the webhooks share it under `/hooks/<app name>`. Must be an absolute http(s) URL with no query or fragment.
url: ""
# How people sign in (see docs/configuration.md), rendered to the
# KRITIKA_AUTH_* variables, which win over the configuration file's `auth`
# block. Each is left out when empty; secrets come from existing Secrets.
auth:
# -- How long a dashboard session lasts (Go duration, 5m to 720h); empty is 12h.
sessionTTL: ""
admin:
# -- The local admin's username; empty is `admin`.
user: ""
passwordSecret:
# -- Existing Secret holding the local admin's password; the local admin exists only while one is set.
name: ""
# -- Key in that Secret.
key: password
oidc:
# -- The sign-in button's label; empty is "SSO".
name: ""
# -- OIDC issuer (https); set, with a client, to sign in through it.
issuer: ""
# -- OAuth client ID at the issuer.
clientId: ""
clientSecretSecret:
# -- Existing Secret holding the client secret.
name: ""
# -- Key in that Secret.
key: client-secret
# -- Scopes to request; empty is openid, email and profile.
scopes: []
# -- ID token or UserInfo claim a role mapping reads as `roles`.
rolesClaim: ""
# -- CEL expression giving a role, or a map of accounts to roles (KRITIKA_AUTH_OIDC_ROLE_MAPPING_EXPR, docs/configuration.md).
roleMappingExpr: ""
# -- Role when the mapping places nobody: none (the default) or member.
defaultRole: ""
github:
# -- Client ID of an OAuth App, or of a GitHub App, to sign in with GitHub.
clientId: ""
clientSecretSecret:
# -- Existing Secret holding the client secret.
name: ""
# -- Key in that Secret.
key: client-secret
# -- CEL expression giving a role, or a map of accounts to roles (KRITIKA_AUTH_GITHUB_ROLE_MAPPING_EXPR).
roleMappingExpr: ""
# The configuration file holds the whole configuration: sign-in (`auth`),
# the GitHub `apps`, the instance's `providers`, `embedding` and `egress`,
# the `defaults` every repository gets, the `repositories` entries keyed
# `owner/*` or `owner/name`, and the `accounts` with their limits and
# providers. Sign-in, one app, one provider, the default models and review
# settings and the embedder also have `KRITIKA_*` environment variables,
# which win over the file; the `auth` values above render some, and
# `extraEnv` can set the others. The file is either inline as `file`,
# rendered into a chart-managed ConfigMap, or an existing ConfigMap with a
# `config.yaml` key. Secret material never goes in the file: it names
# environment variables, `{ env: NAME }`, which `secretEnv` sets from
# existing Secrets. kritika reads the file at startup: the pods carry a
# checksum of `file`, so a change to it rolls them, while a change to an
# `existingConfigMap` needs a restart, for example by stakater's Reloader
# through `deploymentAnnotations`. The leader applies it to the store.
config:
# -- (optional) The configuration file, as YAML: the whole configuration, from `auth` and `apps` to `repositories` and `accounts`. Passed through verbatim, not tpl'd. See docs/configuration.md.
file: {}
# apps:
# - name: github
# accounts: [org-1, user-1]
# clientId: Iv1.example
# privateKey: { env: GITHUB_APP_PRIVATE_KEY }
# webhookSecret: { env: GITHUB_APP_WEBHOOK_SECRET }
# providers:
# openrouter: { type: openrouter, apiKey: { env: OPENROUTER_API_KEY } }
# defaults:
# models: { review: openrouter/vendor/large-model }
# -- Existing ConfigMap holding the file under the `config.yaml` key; takes precedence over `file`. A change to it takes a restart.
existingConfigMap: ""
# -- How often the leader lists each app's open pull requests, its backstop for missed webhooks (KRITIKA_POLL_INTERVAL, Go duration); `0s` turns polling off. Empty is kritika's default, 10m.
pollInterval: ""
# -- How far back a first or long-idle poll looks (KRITIKA_POLL_LOOKBACK, Go duration). Empty is kritika's default, 24h.
pollLookback: ""
# -- How many onboarding index jobs the leader keeps queued or running at once (KRITIKA_ONBOARD_WINDOW). 0 is kritika's default, 4.
onboardWindow: 0
# -- How long the index of a repository that stopped running is kept (KRITIKA_INDEX_GRACE, Go duration). Empty is kritika's default, 720h.
indexGrace: ""
# -- How long a review's transcript is kept, at least 24h (KRITIKA_TRANSCRIPT_RETENTION, Go duration). Empty is kritika's default, 720h.
transcriptRetention: ""
# -- How long a review keeps the diff it was made from, the context it read and the repository files it named, at least 24h (KRITIKA_DIFF_RETENTION, Go duration). Empty is kritika's default, 720h.
diffRetention: ""
# -- Log level: debug, info, warn or error.
logLevel: info
# -- Log format: json or text.
logFormat: json
# -- Review jobs one replica runs at once (KRITIKA_REVIEW_WORKERS); follow-ups share the count. A review or index job holds at most one runner pod, so runner pods never exceed `replicas` × (reviewWorkers + indexWorkers).
reviewWorkers: 2
# -- Index jobs one replica runs at once (KRITIKA_INDEX_WORKERS), rate-limited apart from reviews.
indexWorkers: 1
# -- Extra raw env vars merged into the kritika serve container (advanced).
extraEnv: []
# - name: KRITIKA_LEADER_RETRY_INTERVAL
# value: 30s
# Three Postgres connection strings, each from its own Secret. The
# application role must not own the tables (row-level security), the owner
# role runs migrations and applies configuration on the leader, and the
# runner role is handed to runner Jobs and can only write its own run. On
# CloudNativePG, declare `app` and `runner` under `spec.managed.roles` and
# use the bootstrap owner as `owner`; see the README.
database:
app:
# -- (required) Secret holding the application role's connection URI.
existingSecret: ""
# -- Key in that Secret.
key: uri
# -- Name of the application role, asserted at startup (not superuser, no BYPASSRLS, owns nothing).
role: kritika_app
owner:
# -- (required) Secret holding the owner role's connection URI, used only by the leader for migrations and configuration sync.
existingSecret: ""
# -- Key in that Secret.
key: uri
runner:
# -- (required) Secret holding the runner role's connection URI; referenced by runner Jobs, never read by kritika serve.
existingSecret: ""
# -- Key in that Secret.
key: uri
# -- Name of the runner role, granted only what runner Jobs need.
role: kritika_runner
# The secrets the configuration file names (GitHub App private keys and
# webhook secrets, model keys, sign-in secrets) and the `KRITIKA_*` secret
# variables. Each entry sets the variable `name` in the kritika serve pods
# from `key` of the existing Secret `secretName`; kritika drops it from its
# own environment once read. Runner pods never get them: each is handed its
# own per-run credentials.
# -- Environment variables set from existing Secrets, for the configuration file's `{ env: NAME }` references.
secretEnv: []
# - name: GITHUB_APP_PRIVATE_KEY
# secretName: kritika-bot
# key: private-key.pem
# - name: OPENROUTER_API_KEY
# secretName: kritika-openrouter
# key: api-key
# Runner Jobs: one pod per review or index run, created by kritika serve in the
# release namespace from the same image, running as a service account with
# no permissions and a database role that can only touch its own run. The
# server's Role reaches every Secret in the release namespace, so install
# kritika into a namespace of its own.
runner:
# -- Image for runner Jobs; empty uses the chart's image. The release's `-tools` tag (e.g. `ghcr.io/home-operations/kritika:1.2.3-tools`) adds curl, fd, gh and rg for an agentic review's `agent.commands`.
image: ""
# -- How long a finished Job stays for kubectl before Kubernetes removes it (Go duration); the run row keeps everything the Job knew.
ttl: 10m
# -- Deadline of a runner Job (KRITIKA_RUNNER_DEADLINE, Go duration). Empty is kritika's default, 15m.
deadline: ""
# -- Resources for runner pods (KRITIKA_RUNNER_RESOURCES), copied into the pod spec.
resources: {}
# -- Command-line tools a runner pod mounts from an image for the agent's run tool (KRITIKA_RUNNER_TOOLS), each a `name`, a digest-pinned `image`, the `path` of its binaries and the `commands` it provides. Needs Kubernetes 1.33 or newer, which mounts an image volume with a subPath; the chart refuses to render them on an older cluster.
tools: []
# -- RuntimeClass for runner Jobs (e.g. `gvisor`, `kata`). Advised: a runner parses untrusted repository content and runs what the model asks; a sandboxed runtime keeps it from the node's kernel. Empty uses the cluster default.
runtimeClassName: ""
serviceAccount:
# -- Create the runner ServiceAccount: no permissions, no token mounted, and the chart's `imagePullSecrets` so runner Jobs can pull from a private registry.
create: true
# -- Runner ServiceAccount name; generated from the release name if empty.
name: ""
# -- Annotations for the runner ServiceAccount.
annotations: {}
gateway:
# -- Port of the gateway the kritika serve pods run, on the pods and its Service: the forward proxy runner Jobs are handed as `HTTPS_PROXY`, allowing only the hosts the configuration names (github.com once an app is configured, `egress.allowHosts`), so runner pods need no direct internet egress, and the model and similar-code endpoints a runner calls with a per-run token, so no provider key enters a runner pod.
port: 8082
serviceAccount:
# -- Create the ServiceAccount kritika serve runs as.
create: true
# -- Automount the API token, which kritika serve needs to create runner Jobs.
automount: true
# -- Annotations for the ServiceAccount.
annotations: {}
# -- ServiceAccount name; generated from the release name if empty.
name: ""
rbac:
# -- Create the Role and RoleBinding kritika serve needs: Jobs in the release namespace, their pods and logs, and the Secrets it hands them. Nothing cluster-wide.
create: true
# -- Annotations added to the Deployment (e.g. `reloader.stakater.com/auto: "true"`). Pod-level annotations go in `podAnnotations`.
deploymentAnnotations: {}
# -- Annotations added to the pods.
podAnnotations: {}
# -- Labels added to the pods.
podLabels: {}
# -- Pod-level securityContext (non-root uid/gid 65532, RuntimeDefault seccomp).
podSecurityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
seccompProfile:
type: RuntimeDefault
# -- Container securityContext (no privilege escalation, read-only root filesystem, drops ALL capabilities).
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
service:
# -- Service type of the public Service.
type: ClusterIP
# -- Public port: the webhooks (`POST /hooks/{app}`) and the dashboard.
port: 8080
# -- Metrics and probe port.
metricsPort: 8081
# One Ingress, or one Gateway API HTTPRoute, for `web.url`, to the public
# Service, which serves the webhooks under `/hooks` and the dashboard. The
# host and path come from `web.url`.
ingress:
# -- Expose web.url through an Ingress.
enabled: false
# -- IngressClass name.
className: ""
# -- Ingress annotations.
annotations: {}
# -- Ingress TLS configuration, e.g. `[{hosts: [kritika.example.com], secretName: kritika-tls}]`.
tls: []
httpRoute:
# -- Expose web.url through a Gateway API HTTPRoute.
enabled: false
# -- HTTPRoute apiVersion; empty defaults to gateway.networking.k8s.io/v1.
apiVersion: ""
# -- HTTPRoute annotations.
annotations: {}
# -- HTTPRoute labels.
labels: {}
# -- Gateways (and listeners) this route attaches to.
parentRefs: []
# NetworkPolicies (off by default): one for the service pods (ingress to the
# webhook and metrics ports; egress to DNS, the forges and models over the
# egress ports, and Postgres) and one for runner pods (egress only: DNS, the
# git remote over the egress ports, and Postgres). The Postgres port is
# allowed to any peer because the database may live in another namespace.
networkPolicy:
# -- Create the NetworkPolicies.
enabled: false
# -- Policy flavor for your CNI: "default" (networking.k8s.io/v1 NetworkPolicy), "cilium" (CiliumNetworkPolicy) or "calico" (projectcalico.org/v3 NetworkPolicy).
type: default
# -- Allow DNS egress (UDP/TCP 53); the Cilium flavor allows it to kube-dns alone.
allowDNS: true
# -- TCP ports the service pods may egress to for forges and model endpoints. Runner pods reach the gateway alone.
egressPorts:
- 443
# -- Postgres port allowed for egress.
postgresPort: 5432
# -- Resource requests and limits of the kritika serve pods.
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi
# -- Spread the kritika serve pods across nodes, so a node loss does not take both replicas: a soft constraint, so a one-node cluster still schedules them. Rendered through `tpl`; empty leaves scheduling to Kubernetes.
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: '{{ include "kritika.name" . }}'
app.kubernetes.io/instance: "{{ .Release.Name }}"
# -- Liveness probe, on the metrics port.
livenessProbe:
httpGet:
path: /healthz
port: metrics
periodSeconds: 20
# -- Readiness probe, on the metrics port. A replica is ready once its configuration file has loaded and its listeners are up, before the database answers: until it does, the dashboard shows that kritika is starting and webhooks are refused with a reason, from kritika rather than the ingress.
readinessProbe:
httpGet:
path: /readyz
port: metrics
periodSeconds: 10
# -- Startup probe, on the metrics port. The liveness and readiness probes wait until it passes, so a pod still opening its listeners is not reported unready; it allows a minute.
startupProbe:
httpGet:
path: /healthz
port: metrics
periodSeconds: 2
failureThreshold: 30
# -- Grace period for a clean shutdown: kritika serve keeps accepting webhooks, dashboard requests and model steps for 5s while traffic moves off the pod, stops taking jobs and lets running ones finish for up to 100s, then retries the reviews it cut, and its gateway lets model steps in flight finish for up to 2m.
terminationGracePeriodSeconds: 150
monitoring:
serviceMonitor:
# -- Create a Prometheus Operator ServiceMonitor for the metrics Service (requires its CRDs).
enabled: false
# -- Scrape interval.
interval: 30s
# -- Scrape timeout.
scrapeTimeout: 10s
# -- ServiceMonitor labels.
labels: {}
# -- ServiceMonitor annotations.
annotations: {}
# -- Prometheus metric relabelings.
metricRelabelings: []
# -- Prometheus relabelings.
relabelings: []
podDisruptionBudget:
# -- Create a PodDisruptionBudget when there is more than one replica.
enabled: true
# -- Minimum pods that must stay available, as a count or percentage. Used unless `maxUnavailable` is set.
# @schema
# type: [integer, string]
# @schema
minAvailable: ""
# -- Maximum pods that may be unavailable, as a count or percentage; takes precedence over `minAvailable` when set.
# @schema
# type: [integer, string]
# @schema
maxUnavailable: 1
# -- Node selector for pod scheduling.
nodeSelector: {}
# -- Tolerations for pod scheduling.
tolerations: []
# -- Affinity rules for pod scheduling.
affinity: {}
# -- PriorityClass for the pods. Empty uses the cluster default.
priorityClassName: ""
# -- Additional volumes on the Deployment.
volumes: []
# -- Additional volume mounts on the kritika serve container.
volumeMounts: []
# Config for the chart's `helm test` hook: a one-shot pod that curls the
# metrics Service's /readyz to prove the release serves. Used only by `helm
# test`, never the workload.
tests:
image:
# -- `helm test` connection-pod image; a gcr-mirrored curl, so the test never pulls from Docker Hub.
repository: mirror.gcr.io/curlimages/curl
# -- `helm test` image, pinned as `tag@sha256:digest` so Renovate bumps the tag and its digest together.
tag: "8.22.0@sha256:58adaa4e8dca9c988bae2aba4ab3434a0bb2da16bbe3f92dec39ec7785166777"
# -- `helm test` image pull policy.
pullPolicy: IfNotPresent