Kubernetes local volumes: When and how to use them

Learn how to create Kubernetes local volumes using StorageClass, PersistentVolume and PersistentVolumeClaim. Covers static provisioning, WaitForFirstConsumer binding, and when local volumes make sense.

5 lessons · 16 min · Intermediate

3 minutes reading time

Written by

Civo Team
Civo Team

Marketing Team at Civo

Local volumes give pods direct access to node-local storage while remaining fully managed by Kubernetes. Unlike hostPath volumes, local volumes participate in the scheduler's topology awareness — Kubernetes knows which nodes have local volumes available and schedules pods accordingly.

When to use local volumes

Use a local volume when your workload needs the performance of directly attached storage and cannot tolerate the latency of network-backed storage. The most common use case is databases with high IOPS requirements such as etcd, Cassandra, or PostgreSQL, where network storage introduces unacceptable latency.

Local volumehostPathPVC with dynamic provisioning

Managed by Kubernetes

Yes

No

Yes

Scheduler-aware

Yes

No

Yes

Portable across nodes

No

No

Yes

Supports static provisioning only

Yes

N/A

No

Production-safe

Yes (with caveats)

No

Yes

If your workload does not need node-local performance, use dynamic provisioning with a PVC and StorageClass instead. It is portable, scales automatically, and is easier to operate.

How this demo works

This demo uses hostPath as the underlying storage path for simplicity. In production, local volumes should be backed by dedicated block devices or separate disk partitions, not the host's general filesystem. Using the root filesystem as local volume backing in production can exhaust disk space and destabilise the node.

The demo creates: a StorageClass with no-provisioner, a manually created PV pointing to /opt/data on the node, a PVC that requests storage from that StorageClass, and a pod that mounts the PVC.

Step 1: Create the StorageClass

Local volumes do not support dynamic provisioning. The StorageClass is created with provisioner: kubernetes.io/no-provisioner and volumeBindingMode: WaitForFirstConsumer. The WaitForFirstConsumer mode delays PVC binding until a pod that uses it is scheduled, which allows the scheduler to factor in node constraints before committing the PVC to a specific PV.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
kubectl create -f sc.yaml

Verify:

kubectl get sc

Expected output:

NAME PROVISIONER VOLUMEBINDINGMODE
local-storage kubernetes.io/no-provisioner WaitForFirstConsumer
wait-for-first-consumer

Step 2: Create the PersistentVolume

apiVersion: v1
kind: PersistentVolume
metadata:
name: demo-pv
spec:
storageClassName: local-storage
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /opt/data
persistentVolumeReclaimPolicy: Retain
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- <your-node-name>

Replace <your-node-name> with the name of the node where /opt/data exists. Run kubectl get nodes to find your node name.

kubectl create -f pv.yaml

Step 3: Create the PersistentVolumeClaim

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-pvclaim
spec:
storageClassName: local-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 512Mi
kubectl create -f pvc.yaml

Check the PV and PVC status immediately after creation:

kubectl get pv,pvc

Expected output at this point:

NAME CAPACITY ACCESS MODES STATUS STORAGECLASS
persistentvolume/demo-pv 1Gi RWO Available local-storage
NAME STATUS STORAGECLASS
persistentvolumeclaim/demo-pvclaim Pending local-storage

The PVC stays in Pending status because WaitForFirstConsumer delays binding until a pod is scheduled. This is expected and correct behaviour.

Step 4: Create a pod that uses the PVC

apiVersion: v1
kind: Pod
metadata:
name: task-pv-pod
spec:
volumes:
- name: pv-volume
persistentVolumeClaim:
claimName: demo-pvclaim
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: pv-volume
kubectl create -f pod.yaml

Now check the PV and PVC status again:

kubectl get pv,pvc

Expected output after the pod is scheduled:

NAME CAPACITY ACCESS MODES STATUS STORAGECLASS
persistentvolume/demo-pv 1Gi RWO Bound local-storage
NAME STATUS VOLUME CAPACITY STORAGECLASS
persistentvolumeclaim/demo-pvclaim Bound demo-pv 1Gi local-storage

Both are now Bound. The binding happened when the scheduler assigned the pod to a node.

Check which node the pod is running on:

kubectl get pods -o wide

Step 5: Write data and verify it on the node

Exec into the pod:

kubectl exec -it task-pv-pod -- sh

Create a file:

cd /usr/share/nginx/html
echo "hello" >> index.html
curl localhost

Expected output:

hello

Exit the pod. On the node where the pod is running, the file is now visible at /opt/data/index.html.

Step 6: Verify data persists after pod deletion

Delete the pod:

kubectl delete pod task-pv-pod --force --grace-period=0

Check the PV and PVC:

kubectl get pv,pvc

Expected output:

NAME STATUS STORAGECLASS
persistentvolume/demo-pv Bound local-storage
NAME STATUS STORAGECLASS
persistentvolumeclaim/demo-pvclaim Bound local-storage

Both remain. The data on disk is still at /opt/data/index.html on the node. The pod is gone but the storage objects and their data are independent of it.

Delete the PVC:

kubectl delete persistentvolumeclaim/demo-pvclaim

Check the PV:

kubectl get pv

Expected output:

NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS
demo-pv 1Gi RWO Retain Released

Because the reclaim policy is Retain, the PV moves to Released status rather than being deleted. The data on disk is preserved. To reuse the PV it must be manually reclaimed.

Civo Team
Civo Team

Marketing Team at Civo

Civo is the Sovereign Cloud and AI platform designed to help developers and enterprises build without limits. We bridge the gap between the openness of the public cloud and the rigorous security of private environments, delivering full cloud parity across every deployment. As a team, we are dedicated to providing scalable compute, lightning-fast Kubernetes, and managed services that are ready in minutes. Through CivoStack Enterprise and our FlexCore appliance, we empower organizations to maintain total data sovereignty on their own hardware.

Our mission is to make the cloud faster, simpler, and fairer. By providing enterprise-grade NVIDIA GPUs and streamlined model management, we ensure that high-performance AI and machine learning are accessible to everyone. Built for transparency and performance, the Civo Team is here to give you total control over your infrastructure, your data, and your spend.

View author profile