Persistent storage in Kubernetes starts with a claim that asks the cluster for a volume instead of tying a Pod to a node-local path. A PersistentVolumeClaim lets a workload request capacity and access mode from a StorageClass so application data can survive container restarts and Pod replacement.

PersistentVolumeClaim objects are namespaced. The StorageClass controls how a matching PersistentVolume is provisioned, and some classes wait until a Pod consumes the claim before binding the volume to a node.

A small lab namespace, a 1 GiB ReadWriteOnce claim, and a test Pod that writes a file through the mounted volume prove both binding and real use. Replace standard with the StorageClass used by your cluster, then keep the claim in place for real workloads instead of running the lab cleanup.

Steps to create a Kubernetes PersistentVolumeClaim:

  1. List the available StorageClasses.
    $ kubectl get storageclass
    NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
    standard (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  3m28s

    The PVC manifest selects standard. A class with WaitForFirstConsumer can leave the claim Pending until a Pod that uses it is scheduled.

  2. Create a namespace for the PVC test.
    $ kubectl create namespace storage-demo
    namespace/storage-demo created

    Use the target application namespace instead of storage-demo when creating a real claim.

  3. Save the PersistentVolumeClaim manifest.
    app-data-pvc.yaml
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: app-data
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: standard
      resources:
        requests:
          storage: 1Gi

    ReadWriteOnce allows the volume to be mounted read-write by workloads on one node at a time. Change storageClassName to the class that should provision the volume in your cluster.

  4. Apply the claim to the namespace.
    $ kubectl apply -f app-data-pvc.yaml --namespace storage-demo
    persistentvolumeclaim/app-data created
  5. Check the initial claim phase.
    $ kubectl get pvc app-data --namespace storage-demo
    NAME       STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
    app-data   Pending                                      standard       <unset>                 0s

    Pending is expected here when the selected StorageClass uses WaitForFirstConsumer. Classes with immediate binding may show Bound before a Pod exists.

  6. Save a test Pod manifest that mounts the claim.
    pvc-writer-pod.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: pvc-writer
    spec:
      restartPolicy: Never
      containers:
        - name: writer
          image: busybox:1.36
          command: ["sh", "-c", "echo pvc-ok > /data/check.txt; sleep 300"]
          volumeMounts:
            - name: app-data
              mountPath: /data
      volumes:
        - name: app-data
          persistentVolumeClaim:
            claimName: app-data

    The container writes one file to the mounted volume and stays running long enough for the readback check.

  7. Create the test Pod in the same namespace.
    $ kubectl apply -f pvc-writer-pod.yaml --namespace storage-demo
    pod/pvc-writer created
  8. Wait for the Pod to become ready.
    $ kubectl wait --for=condition=Ready pod/pvc-writer --namespace storage-demo --timeout=120s
    pod/pvc-writer condition met
  9. Wait for the claim to bind.
    $ kubectl wait --for=jsonpath='{.status.phase}'=Bound pvc/app-data --namespace storage-demo --timeout=90s
    persistentvolumeclaim/app-data condition met

    The Bound phase means the claim has been matched with a PersistentVolume.

  10. Read the file written through the mounted claim.
    $ kubectl exec pvc-writer --namespace storage-demo -- cat /data/check.txt
    pvc-ok
  11. Inspect the bound claim.
    $ kubectl get pvc app-data --namespace storage-demo
    NAME       STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
    app-data   Bound    pvc-ae56be57-ff92-4c43-96b4-454eed635cd6   1Gi        RWO            standard       <unset>                 7s
  12. Delete the temporary test Pod when the claim should stay available.
    $ kubectl delete pod pvc-writer --namespace storage-demo
    pod "pvc-writer" deleted from storage-demo namespace
  13. Delete the lab namespace only when all test resources should be removed.
    $ kubectl delete namespace storage-demo
    namespace "storage-demo" deleted

    Skip this command for a real application namespace. Deleting the namespace also removes the PersistentVolumeClaim and the dynamically provisioned volume.