How-to guide / Package task guides
Deploy Valkey / Redis
Use the valkey package to deploy a Redis-compatible data store on the
official Valkey image: a StatefulSet with a volume per pod, a Service for
clients, password authentication, and, on clusters that span several nodes,
Sentinel failover.
Add the Valkey instance and its password
Section titled “Add the Valkey instance and its password”Add the package, and a secret holding its password, in the namespace where the applications that use it run:
16 instances.cache = {17 valkey = {18 package = packages.valkey;19 config.storage.size = "2Gi";20 };21
22 # Outside local development, give the secret a SOPS `source` with23 # keys = [ "password" ], or use a secret-ref to an existing Secret.24 valkey-password = {25 package = packages.secret;26 aliases = [ "valkeyPassword" ];27 config.stringData.password = "development-only-password";28 };29 };The valkeyPassword dependency is required and auth is always on. It
resolves to a secret or secret-ref instance in Valkey’s namespace that
declares the key password. The inline password in the example is for local
development. For a deployed environment, give the secret a SOPS source with
keys = [ "password" ], or point a secret-ref instance at a Secret managed
outside Kix. When the password of a secret-ref changes, the pods do not
restart on their own; use reloader’s withReloadOn to roll them. The password
is used byte for byte, so a trailing newline in the Secret is part of it, and
clients must send it too. An empty password stops the pod from starting.
Each pod gets a claim of storage.size (8 GiB unless set). The claim’s class
is storage.storageClassName when set, else the class of the cluster’s
storageClasses provider, else the cluster default.
Choose the topology
Section titled “Choose the topology”The availability level decides the topology. Below node, Valkey runs one
standalone pod. From node up, sentinel.enabled defaults on and the
instance runs three pods: one primary, two replicas, and a Sentinel sidecar
in each pod that promotes a replica when the primary is lost. Set
sentinel.enabled and replicas on the instance to choose explicitly.
Evaluation refuses more than one pod without Sentinel, fewer than three with
it, and a sentinel.quorum that the remaining Sentinels could not reach once
the primary’s pod is gone.
Change Valkey’s configuration through settings, which renders valkey.conf
directives, and Sentinel’s through sentinel.primarySettings:
1instances.cache.valkey.config = {2 settings.maxmemory-policy = "allkeys-lru";3 sentinel.primarySettings.down-after-milliseconds = 5000;4};maxmemory defaults to 70% of resources.limits.memory, leaving room for
replication buffers and the copy-on-write pages of an AOF rewrite. With the
default noeviction policy, writes fail once the dataset is full. Directives
the package sets itself or relies on, such as port, bind, the password,
replication, and rename-command, are refused, and so is a value with a line
break.
When the cluster has a prometheus provider, each pod also runs a
redis_exporter sidecar, and the instance gets a ServiceMonitor and alerts.
The sidecar is part of the pod template, so adding the first provider, or
setting metrics.enabled = false, restarts every pod.
Connect an application
Section titled “Connect an application”Valkey’s out follows the Redis contract, kix.contracts.redis.
Applications take it as their valkey dependency and read these fields:
| Key | Value |
|---|---|
out.host, out.port | The Service that selects every Valkey pod, port "6379" |
out.credentials | The password secret: valkey.out.credentials.out.keyRef "password" |
out.tls | false: Valkey serves no TLS |
out.sentinel | null, or with Sentinel { port = "26379"; primarySet = "primary"; } |
A package checks the dependency with
deps.valkey.assertions = dep: kix.contracts.redis.assertions who dep;.
A consumer must run in Valkey’s namespace, because Kubernetes resolves a
secretKeyRef only in the pod’s own namespace. Dependency injection does not
offer a Valkey instance to a consumer in another namespace, and an explicit
ref to one fails evaluation.
With Sentinel on, the Service also routes to replicas, which refuse writes. A
client that writes asks Sentinel at out.host and out.sentinel.port for the
primary of out.sentinel.primarySet. It sends Sentinel no password:
Sentinel’s default user takes none, and an AUTH with a password is an error.
That user may read Sentinel’s state, which covers the discovery calls of
common clients (SENTINEL GET-PRIMARY-ADDR-BY-NAME, SENTINEL MASTERS,
SENTINEL REPLICAS, SENTINEL SENTINELS), and may not change it. Only the
sentinel-admin user, with the instance password, can reconfigure Sentinel.
A package whose client cannot use Sentinel asks for a standalone Valkey and refuses one that runs Sentinel anyway, such as an import:
1deps.valkey = {2 config.sentinel.enabled = false;3 assertions =4 dep:5 kix.contracts.redis.assertions who dep6 ++ lib.optional (kix.contracts.redis.usesSentinel dep) {7 assertion = false;8 message = "this package has no Sentinel client; set sentinel.enabled = false on the valkey instance.";9 };10};Use a Redis server outside the cluster
Section titled “Use a Redis server outside the cluster”To use a server Kix does not run, such as ElastiCache, replace the valkey
instance with a kix.mkImport that sets the contract’s fields. Put the
import in the application’s namespace, beside the secret that holds the
password:
1{ ref, ... }:2{3 instances.app = {4 redis-login = {5 package = packages.secret;6 config.source = ./secrets/redis-login.sops.yaml;7 config.keys = [ "password" ];8 };9 valkey.package = kix.mkImport {10 scope = "namespace";11 out = {12 host = "app-cache.abc123.euw1.cache.amazonaws.com";13 port = "6379";14 tls = true;15 credentials = ref.app.redis-login;16 };17 };18 };19}Leave out credentials for a server that takes no password. An import
without a password can keep the default scope = "cluster" and serve every
namespace. Not every application can connect over TLS: Harbor and Immich
refuse a server with tls = true, and n8n turns TLS on for it.
Check the generated resources
Section titled “Check the generated resources”Evaluate the cluster:
❱ kix check how-to-package-valkey
TOOL RESULT DETAILS
eval pass 14 manifests evaluated
scorecard pass 0 errors, 1 warnings, 0 info Inspect the StatefulSet and client Service:
❱ kix build how-to-package-valkey --output json
[
{
"apiVersion": "apps/v1",
"kind": "StatefulSet",
"metadata": {
"name": "valkey",
"namespace": "cache"
},
"spec": {
"replicas": 1,
"serviceName": "valkey-headless",
"containers": [
{
"name": "valkey",
"image": "docker.io/valkey/valkey:8.1.10@sha256:640c5e62cea04b6d6f2084232651d0cc70362d31f4f805e7be94dbed6855e8f2",
"ports": [
{
"containerPort": 6379,
"name": "valkey",
"protocol": "TCP"
}
]
}
],
"volumeClaimTemplates": [
{
"metadata": {
"name": "data"
},
"spec": {
"accessModes": [
"ReadWriteOnce"
],
"resources": {
"requests": {
"storage": "2Gi"
}
},
"storageClassName": "standard"
}
}
]
}
},
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "valkey",
"namespace": "cache"
},
"spec": {
"ports": [
{
"name": "valkey",
"port": 6379,
"protocol": "TCP",
"targetPort": 6379
}
]
}
}
] The StatefulSet requests the configured 2 GiB volume and uses the headless
Service for stable pod names. Applications connect to the valkey Service on
port 6379.
Deploy and connect
Section titled “Deploy and connect”Deploy the cluster and check the instance:
❱ kix deploy how-to-package-valkey❱ kix status how-to-package-valkey To test it locally, forward the service port:
❱ kix pf how-to-package-valkey valkey 6379:6379 While the forward is running, use a Redis-compatible client from another terminal:
❱ REDISCLI_AUTH=development-only-password valkey-cli ping A successful connection returns PONG. Stop the port-forward with Ctrl-C.
If the pod remains pending, inspect its PVC and confirm that the requested
StorageClass exists and can provision ReadWriteOnce volumes in the target
cluster.