Skip to content
kix /docs
Install the CLI

How-to guide Package task guides

Deploy Valkey / Redis

Deploy a persistent Redis-compatible Valkey service with the Kix package catalogue.

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 package, and a secret holding its password, in the namespace where the applications that use it run:

how-to/package-stacks/valkey-cluster.nix
instances.cache = {
valkey = {
package = packages.valkey;
config.storage.size = "2Gi";
};
# Outside local development, give the secret a SOPS `source` with
# keys = [ "password" ], or use a secret-ref to an existing Secret.
valkey-password = {
package = packages.secret;
aliases = [ "valkeyPassword" ];
config.stringData.password = "development-only-password";
};
};

View source on GitHub ↗

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.

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:

cluster.nix
instances.cache.valkey.config = {
settings.maxmemory-policy = "allkeys-lru";
sentinel.primarySettings.down-after-milliseconds = 5000;
};

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.

Applications read the connection from the instance’s out:

KeyValue
out.fqdn, out.portThe Service that selects every Valkey pod, port 6379
out.authSecret, out.authPasswordKeyThe password secret and its key: valkey.out.authSecret.out.keyRef valkey.out.authPasswordKey
out.sentinelPort, out.primarySetWith Sentinel only: port 26379 on the same Service, and the name Sentinel monitors the primary under

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.fqdn and out.sentinelPort for the primary of out.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:

package default.nix
deps.valkey = {
config.sentinel.enabled = false;
assertions =
dep:
lib.optional (dep.out ? sentinelPort) {
assertion = false;
message = "this package has no Sentinel client; set sentinel.enabled = false on the valkey instance.";
};
};

Evaluate the cluster:

kix-examples/
❱ 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-examples/ output excerpt
❱ 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 the cluster and check the instance:

kix-examples/
❱ kix deploy how-to-package-valkey
❱ kix status how-to-package-valkey

To test it locally, forward the service port:

kix-examples/
❱ 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.