Skip to content
kix /docs
Install the CLI

How-to guide Platform capabilities

Configure storage classes and PVCs

Give a cluster the StorageClasses its claims use, and configure PVC instances that consume them.

Kix models storage in two layers. A cluster-level storageClasses provider exports out.storageClassName, the class a claim gets when it names none, and namespace-scoped persistent-volume-claim instances request volumes. Application packages consume the claims through a storage dependency.

The kind, k3s, GKE, GKE Autopilot, AKS, OVH, and Scaleway flavors import the environment’s default StorageClass as a storageClasses provider. If that class has the provisioner, binding mode, reclaim policy, and access modes you need, use it directly and create only the PVC instance below.

The rke2, kubeadm, talos, openshift, and EKS flavors import no class. On those clusters, and on a cluster with no flavor, a claim that names no class fails the build until you add a provider or set the claim’s class.

The storage-classes package creates StorageClasses for CSI drivers that already run in the cluster and provides the storageClasses role:

clusters/example.nix
# rancher.io/local-path exists only where local-path-provisioner is
# installed; name your cluster's CSI driver here.
storage-classes = {
package = packages.storage-classes;
config.classes.local-path = {
provisioner = "rancher.io/local-path";
volumeBindingMode = "WaitForFirstConsumer";
};
};

View source on GitHub ↗

Each entry of config.classes is one StorageClass, keyed by its name. The fields are the StorageClass v1 fields: provisioner, parameters, reclaimPolicy, volumeBindingMode, allowVolumeExpansion, mountOptions, allowedTopologies, labels, and annotations. Use the values the installed driver supports. Creating a StorageClass does not install its provisioner; rancher.io/local-path works only where local-path-provisioner runs.

The apiserver refuses changes to provisioner, parameters, reclaimPolicy, and volumeBindingMode. Changing one means deleting and recreating the class.

On a flavor that imports a default class, the storage-classes instance replaces the import for Kix consumers. The environment’s class stays the cluster default for claims Kix does not create. See Shadow a flavor-provided import with a managed package.

out.storageClassName is the only class when the instance has one. With several classes, it is the class annotated storageclass.kubernetes.io/is-default-class = "true". With several classes and no annotation, a consumer that names no class fails the build, and each claim sets its own storageClassName.

The annotation also makes the class the cluster default, so Kix refuses it on a flavor whose environment already has a default class. Annotate at most one class: two classes annotated default in one build fail the build.

A CSI driver package that Kix installs renders its own classes and does not claim the role. Give its instance aliases = [ "storageClasses" ] to make it the cluster’s provider, or wire one consumer with deps.storageClasses = ref.<namespace>.<instance>.

When the environment has a class and the flavor does not import it, declare the import yourself so claims can use it:

clusters/example-import.nix
kube-system.platform-storage.package = kix.mkImport {
kind = "StorageClass";
apiVersion = "storage.k8s.io/v1";
roles = [ "storageClasses" ];
out = {
name = "local-path";
storageClassName = "local-path";
};
};

View source on GitHub ↗

kind, apiVersion, and out.name identify the imported StorageClass. out.storageClassName is the class claims that name none get; set both to the class’s name.

Create a persistent-volume-claim instance in the application’s namespace:

clusters/test-doc-how-tos.nix
instances.apps.data = {
package = packages."persistent-volume-claim";
config = {
size = "20Gi";
accessModes = [ "ReadWriteOnce" ];
# Omit storageClassName to use the storageClasses provider.
};
};

View source on GitHub ↗

size is required. With storageClassName omitted, the package reads the class from the storageClasses provider. Set config.storageClassName when this claim should use another class. An explicit value takes precedence over the dependency.

Select only access modes the provider supports. ReadWriteOnce is common for block storage; ReadWriteMany requires a provider that supports shared mounts, and Kix does not check that it does.

Evaluate the cluster and inspect the StorageClass and claim together:

❱ kix check doc-how-tos
❱ kix build doc-how-tos --output json | jq '.[] | select(.kind == "StorageClass" or .kind == "PersistentVolumeClaim") | {kind, name: .metadata.name, spec}'

Changing a bound claim’s StorageClass is normally an immutable, stateful migration. Review the diff and the storage provider’s reclaim behavior before deploying that change.

See Provision persistent storage for mounting the resulting claim into an application package.