Skip to content
kix /docs
Install the CLI

How-to guide Package task guides

Provision persistent storage

Create a first-class PVC instance and mount it into an application package.

Provision storage as a persistent-volume-claim instance, then consume that instance as a package dependency. This keeps the claim visible in the deploy graph and lets Kix treat it as stateful during change planning.

Kix requires this pattern. Storage size, StorageClass, and lifecycle belong to the cluster operator, so evaluation rejects application packages that create their own PersistentVolumeClaims and directs them to use a storage dependency. Packages that must create claims, such as the canonical persistent-volume-claim package or a Helm chart with persistence enabled, need an explicit meta.unsafe = [ "raw-pvc" ] grant. The scorecard reports every such grant for review.

This guide assumes your target cluster flavor provides a default StorageClass (kind, k3s, GKE, GKE Autopilot, AKS, OVH, and Scaleway do; rke2, kubeadm, talos, openshift, and EKS do not), or that you know the StorageClass name to request.

Create the PVC in the same namespace as the application:

how-to/package-stacks/storage-cluster.nix
instances.storage-example.storage = {
package = packages."persistent-volume-claim";
config = {
size = "5Gi";
accessModes = [ "ReadWriteOnce" ];
};
};
instances.storage-example.worker = {
package = storageApp;
};

View source on GitHub ↗

The PVC package advertises the storage alias. The application’s storage build argument therefore resolves to the claim in the same namespace.

Set size to a Kubernetes storage quantity such as "5Gi". It is required: a generic claim cannot know how much its consumer stores. A package that owns its own claim template, such as a StatefulSet’s volume, may default the size because it knows what it stores.

Choose access modes supported by the storage provider. If the cluster does not provide a default through its flavor, or the application needs another class, set config.storageClassName = "<class-name>" on the PVC instance. Other PVC spec fields, such as volumeMode, dataSource, selector, and volumeName, go in config.extraSpec.

When a cluster sets availablePackages and declares no claim, Kix auto-instantiates persistent-volume-claim for a package’s required storage dependency. That claim has no size, so the build fails with The option `instances.<namespace>.persistent-volume-claim.config.size' was accessed but has no value defined after a [kix] auto-instantiated: persistent-volume-claim (<namespace>) trace. Declare the claim as a full instance with its size, as in the snippet above.

Accept storage in the package’s build arguments, then create a PVC mount:

how-to/package-stacks/storage-app.nix
dataMount = kix.mount.pvc storage {
mountPath = "/var/lib/app";
};

View source on GitHub ↗

The mount uses the resolved claim’s name and carries its dependency edge.

Apply the mount before sealing the workload:

how-to/package-stacks/storage-app.nix
deployment =
{
name = scope.instanceName;
spec = {
replicas = 1;
selector.matchLabels = scope.selectorLabels;
template.spec.containers = [
{
name = "worker";
image = "docker.io/library/busybox:1.37";
command = [
"sh"
"-c"
"sleep infinity"
];
}
];
};
}
|> kix.withMounts [ self.dataMount ]
|> scope.mkDeployment;

View source on GitHub ↗

kix.withMounts adds the PVC volume and the matching container mount. Use kix.withMountsOn when only one container in a multi-container pod should receive it.

Evaluate the cluster:

kix-examples/
❱ kix check how-to-package-storage
 TOOL       RESULT  DETAILS                      
 eval       pass    11 manifests evaluated       
 scorecard  pass    0 errors, 9 warnings, 2 info

Inspect the generated claim and workload volume:

kix-examples/ output excerpt
❱ kix build how-to-package-storage --output json
[
  {
    "apiVersion": "apps/v1",
    "kind": "Deployment",
    "metadata": {
      "name": "worker",
      "namespace": "storage-example"
    },
    "podSpec": {
      "containers": [
        {
          "name": "worker",
          "volumeMounts": [
            {
              "mountPath": "/var/lib/app",
              "name": "storage"
            }
          ]
        }
      ],
      "volumes": [
        {
          "name": "storage",
          "persistentVolumeClaim": {
            "claimName": "storage"
          }
        }
      ]
    }
  },
  {
    "apiVersion": "v1",
    "kind": "PersistentVolumeClaim",
    "metadata": {
      "name": "storage",
      "namespace": "storage-example"
    },
    "spec": {
      "accessModes": [
        "ReadWriteOnce"
      ],
      "resources": {
        "requests": {
          "storage": "5Gi"
        }
      },
      "storageClassName": "standard"
    }
  }
]

In this kind-based example, the flavor supplies the standard StorageClass. The PVC requests 5 GiB, and the Deployment mounts it at /var/lib/app.

The kind StorageClass stores data inside the kind node. It is suitable for local testing, but deleting the kind cluster deletes that data. Check the reclaim policy and failure behavior of your real storage provider before using the same configuration for application data.