A practical guide to building a safe Kubernetes volumes lab on your local machine for testing PersistentVolumes, PersistentVolumeClaims, StorageClasses, and pod storage configurations.
Intro
Kubernetes volumes are a fundamental building block for stateful applications, yet they are often misunderstood. A local lab gives you a safe, isolated environment to experiment with PersistentVolumes (PVs), PersistentVolumeClaims (PVCs), and StorageClasses without risking production data. This guide walks through setting up such a lab using common tools like Minikube or kind, creating local storage, and running practical tests. By the end, you will have a repeatable workflow for learning and debugging volume behavior.
We will start with environment setup, then create a StorageClass and PVC, attach the volume to a pod, verify persistence, explore failure modes and recovery, and finish with an operations checklist. Each step includes concrete commands, YAML snippets, and expected outputs so you can follow along.
Version and Environment Inventory
Before starting, ensure you have the following installed and compatible:
| Component | Recommended Version | Notes |
|---|---|---|
| Kubernetes | v1.24 or later | Minikube or kind both work |
| kubectl | v1.24 or later | Match minor version to cluster |
| Minikube | v1.26 or later | Or kind v0.14 or later |
| Container runtime | Docker or containerd | Minikube can use its own VM |
| Host OS | Linux, macOS, or Windows | Linux is easiest for local volumes |
This lab uses Minikube with the default Docker driver. Verify your environment with:
kubectl version --client
# Example output: Client Version: v1.26.3
minikube version
# Example output: minikube version: v1.29.0
Start your cluster:
minikube start --driver=docker --kubernetes-version=v1.26.3
# Example output: Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default.
Verify the cluster is up:
kubectl get nodes
# Expected output:
# NAME STATUS ROLES AGE VERSION
# minikube Ready control-plane 1m v1.26.3
If your environment uses kind, the steps are similar, but hostPath volumes will be on the kind node container rather than a VM. The commands for creating PVCs and pods remain the same.
Safe Configuration Path
For a safe lab, use a StorageClass with volumeBindingMode: Immediate and a local path provisioner. Minikube provides a built-in standard StorageClass that dynamically provisions hostPath volumes. Alternatively, you can create your own StorageClass for more control.
First, inspect existing StorageClasses:
kubectl get storageclass
# Expected output:
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# standard (default) k8s.io/minikube-hostpath Delete Immediate false 1m
The output shows a default StorageClass named standard with provisioner k8s.io/minikube-hostpath. This provisioner creates a directory under /tmp/hostpath-provisioner/ on the Minikube node for each volume. Because the reclaim policy is Delete, removing the PVC will delete the associated directory.
If you need a custom StorageClass, create a YAML file custom-sc.yaml:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-hp
provisioner: k8s.io/minikube-hostpath
reclaimPolicy: Retain
volumeBindingMode: Immediate
Apply it:
kubectl apply -f custom-sc.yaml
# Expected output: storageclass.storage.k8s.io/local-hp created
For this lab, we will use the default standard class for simplicity.
Now create a PVC to request storage from this StorageClass. Save the following as pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: lab-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Apply it:
kubectl apply -f pvc.yaml
# Expected output: persistentvolumeclaim/lab-pvc created
Check the PVC status:
kubectl get pvc lab-pvc
# Expected output:
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# lab-pvc Bound pvc-1234abcd-56ef-7890-ghij-klmnopqrstuv 1Gi RWO standard 5s
A PV was automatically created. View it:
kubectl get pv
# Expected output:
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# pvc-1234abcd-56ef-7890-ghij-klmnopqrstuv 1Gi RWO Delete Bound default/lab-pvc standard 10s
The PV is bound to the PVC. Note the reclaim policy is Delete, meaning when the PVC is deleted, the PV and underlying data will be removed.
Now deploy a pod that uses this PVC. Save the following as pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: volume-test
spec:
containers:
- name: app
image: nginx:latest
volumeMounts:
- mountPath: /usr/share/nginx/html
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: lab-pvc
Apply the pod:
kubectl apply -f pod.yaml
# Expected output: pod/volume-test created
Verify the pod is running:
kubectl get pod volume-test
# Expected output:
# NAME READY STATUS RESTARTS AGE
# volume-test 1/1 Running 0 10s
Verification and Diagnostics
To verify the volume is working, write a file from inside the pod and then check it from the host or another pod.
First, exec into the pod and create a file:
kubectl exec -it volume-test -- sh -c 'echo Hello from volume > /usr/share/nginx/html/test.txt'
# No output if successful
Read the file from the pod:
kubectl exec volume-test -- cat /usr/share/nginx/html/test.txt
# Expected output: Hello from volume
Now, inspect the host path where the volume is mounted. For Minikube, you can use minikube ssh:
minikube ssh
# Inside the minikube VM
sudo ls /tmp/hostpath-provisioner/default/lab-pvc
# Expected output: test.txt
sudo cat /tmp/hostpath-provisioner/default/lab-pvc/test.txt
# Expected output: Hello from volume
This confirms the volume persists on the host. Exit the SSH session with exit.
To test persistence across pod restarts, delete the pod and recreate it:
kubectl delete pod volume-test
# Expected output: pod "volume-test" deleted
kubectl apply -f pod.yaml
# Expected output: pod/volume-test created
After the pod starts, check the file still exists:
kubectl exec volume-test -- cat /usr/share/nginx/html/test.txt
# Expected output: Hello from volume
To see detailed volume information, describe the PVC and PV:
kubectl describe pvc lab-pvc
# Look for Events:
# Normal ProvisioningSucceeded 10s k8s.io/minikube-hostpath_minikube Successfully provisioned volume pvc-1234abcd-56ef-7890-ghij-klmnopqrstuv
kubectl describe pv pvc-1234abcd-56ef-7890-ghij-klmnopqrstuv
# Look for Source:
# Type: HostPath (path: /tmp/hostpath-provisioner/default/lab-pvc)
Check storage capacity and usage from inside the pod:
kubectl exec volume-test -- df -h /usr/share/nginx/html
# Example output:
# Filesystem Size Used Avail Use% Mounted on
# /dev/sda1 9.7G 1.2G 8.5G 12% /usr/share/nginx/html
Failure Modes and Recovery
Common failures in a local lab include PVC stuck in Pending, pod failing to mount, or data loss due to reclaim policy.
PVC Pending
If the PVC is not bound, check StorageClass and provisioner:
kubectl describe pvc lab-pvc
# Look for Events:
# Warning ProvisioningFailed 10s k8s.io/minikube-hostpath_minikube failed to provision volume with StorageClass "standard": ...
If using a custom StorageClass, ensure the provisioner is correct and available. For Minikube, the standard class works out of the box.
Pod Mount Errors
If the pod fails to start with a mount error, check the PVC status and the pod events:
kubectl get pvc
kubectl describe pod volume-test
# Look for Events:
# Warning FailedMount 30s kubelet MountVolume.SetUp failed for volume "pvc-..." : ...
Sometimes the issue is that the volume is already mounted by another pod with ReadWriteOnce on a different node. In a single-node Minikube, this is rare.
Data Loss on PVC Deletion
The default reclaim policy for the standard StorageClass is Delete. If you delete the PVC, the PV and hostPath data are removed.
kubectl delete pvc lab-pvc
# Expected output: persistentvolumeclaim "lab-pvc" deleted
If you need to retain data, change the reclaim policy to Retain on the PV before deleting the PVC. However, for a lab, you can recreate the PVC and pod from scratch.
Recovery Steps
- Check cluster events:
kubectl get events --sort-by=.metadata.creationTimestamp - Verify StorageClass:
kubectl get sc - Inspect hostPath on the node if using hostPath:
minikube sshand browse/tmp/hostpath-provisioner/... - Recreate resources if necessary.
To reset the lab completely, delete all created resources:
kubectl delete pod volume-test
kubectl delete pvc lab-pvc
# No output, but resources are deleted
Then reapply your YAML files.
Operations Checklist
Use this checklist for repeatable lab operations:
| Step | Command / Action | Expected Result |
|---|---|---|
| Start cluster | minikube start | Cluster ready |
| Check StorageClass | kubectl get sc | standard listed |
| Apply PVC | kubectl apply -f pvc.yaml | PVC created |
| Verify PVC bound | kubectl get pvc | STATUS Bound |
| Apply pod | kubectl apply -f pod.yaml | Pod running |
| Write test file | kubectl exec ... -- sh -c 'echo test > /mnt/test.txt' | No error |
| Read test file | kubectl exec ... -- cat /mnt/test.txt | Output test |
| Delete pod and recreate | kubectl delete pod ... && kubectl apply -f pod.yaml | Data persists |
| Clean up | kubectl delete pod, pvc | Resources removed |
For deeper inspection:
kubectl describe pvcshows provisioning events.kubectl describe pvshows volume source details.minikube sshallows host-level file checks.
Keep YAML files version-controlled for reproducible labs.
Conclusion
You now have a functional Kubernetes volumes lab on your local machine. You learned how to set up a cluster, create a StorageClass, provision a PVC, attach it to a pod, verify data persistence, and recover from common failures. This lab serves as a foundation for exploring more advanced topics like StatefulSets, dynamic provisioning with different storage backends, and CSI drivers. Remember to start with a narrow scenario and expand gradually, as suggested by the research evidence for effective local development. Keep your environment clean and your YAML files organized for repeatable learning.
To extend this lab, experiment with different access modes, reclaim policies, and custom StorageClasses. Try deploying multiple pods sharing a ReadWriteMany volume, or use a StatefulSet to manage persistent storage for each replica. These exercises will deepen your understanding of Kubernetes storage and prepare you for production scenarios.