ADR-064: A cache is a platform-rendered single-node Valkey StatefulSet, without an operator; its password is generated once in the cluster
Status: Accepted (2026-10-27)
Amended by: ADR-072: A generated credential is written exactly once, by a create-only Job that cannot write it twice (point 3, the password is written by a credentials Job)
Builds on: ADR-018: Secrets come from External Secrets Operator; git holds references, never values (secrets from External Secrets Operator), ADR-022: Kyverno guard rails, checked before merge and enforced in the cluster (guard rails), ADR-057: Resources are named and typed; each type has a fixed contract; any resource instance may be external (named, typed resources, the cache contract slot), ADR-058: Images and operators are pinned exactly per release; the platform installs the operators a cluster needs as Argo CD applications (pinned versions, operators under operators/), ADR-062: An environment is backed up by Velero, installed once per cluster; persistent volumes by file-system backup; databases, caches and the target bucket are left out (caches left out of environment backups), spec 118; used by spec 120 (scaling on a list length) and spec 121 (upgrades)
Context
Section titled “Context”Spec 118 delivers the cache resource type: a Redis-protocol key-value store per environment, a Secret <system>-<name>-app with the
keys url, host, port and password, and the variables REDIS_URL, REDIS_HOST, REDIS_PORT and REDIS_PASSWORD whether the
cache runs in the cluster or at a provider. It runs Valkey (BSD-licensed; Redis 8 is offered only under AGPLv3, RSAL or SSPL), one
node per environment, with no high availability, no backups and no data that must survive an upgrade.
Databases (ADR-058) and buckets (ADR-059) run through operators the platform installs per cluster. An operator earns its place when the engine needs failover, backups or multi-object coordination. A single Valkey node needs none of that: a StatefulSet, a Service and a Secret describe it completely, and every Valkey operator or chart would add a CRD, a cluster-wide controller and a second version to pin and upgrade.
Decision
Section titled “Decision”- The renderer writes a cache instance itself: one
StatefulSet<system>-<name>(one replica, the officialvalkey/valkeyimage pinned per major in the versions table), oneClusterIPService on port 6379, and, withpersistence: true, a volume fromvolumeClaimTemplatesholding an append-only file synced every second. No operator, CRD or Helm chart is installed for a cache;operators: ownerchanges nothing for it and doctor has no row for it. - Sizing is the server’s memory, not the node’s: the container’s memory limit is
memory, and the server’smaxmemoryis 80 % of it with the declared eviction policy, so the server evicts or refuses writes before the kernel kills it. - The password is generated in the cluster once. (Amended by ADR-072: A generated credential is written exactly once, by a create-only Job that cannot write it twice: ESO
CreatedOncewrote it twice, #316.) A credentials Job creates the four keys of<system>-<name>-appif the Secret is absent (letters and digits only, so theurlneeds no escaping); the server reads the password from the same Secret. Renders, syncs, restarts and image upgrades never change it. No secret value is in git. - One connection Secret per cache instance is the contract other specs build on (spec 120’s scaler reads the same Secret). Every
component that uses the cache shares database
0and the password; the host is namespace-qualified (<n>.<ns>.svc). - Every object of a cache instance carries
app.kubernetes.io/name: <name>, which is how ADR-062’s Velero schedule leaves cache objects and volumes out of every environment backup. Cache data is never backed up. - The workload satisfies the guard rails without being matched by them: non-root, read-only root filesystem, no privilege escalation, all capabilities dropped, memory limit and requests. ADR-022’s policies (Deployments only) do not change.
Consequences
Section titled “Consequences”- A cache costs one pod (and one volume with persistence) per environment; it needs no operator and no External Secrets Operator.
/platform:doctorandcluster-upknow nothing about caches; a local cluster runs one as soon as a component uses it.- A change of
persistenceorstoragestops at the StatefulSet’s immutablevolumeClaimTemplates: delete it withkubectl delete statefulsets.apps <system>-<name> -n <system>-<env> --cascade=orphanand sync; the pod restarts and an existing volume is reused, not resized. No sync option replaces the StatefulSet, so ordinary syncs never restart or empty a cache. - The Valkey pins move under spec 121’s rules (environments in order, downgrades refused, no backup precondition for a cache).
- High availability (replicas, Sentinel, cluster mode) would need a different design, most likely an operator; this ADR would then be superseded for that case.
Not included
Section titled “Not included”- Redis itself, or a choice between Redis and Valkey.
- Per-component users, ACLs or database numbers; TLS inside the cluster; password rotation.
- Valkey modules and server settings beyond
memory,maxmemoryPolicy,persistenceandstorage. - Creating caches at a cloud provider: an external cache’s Secret comes from the secret store (ADR-057).