Volumes, PVCs & Persistent Storage
Containers are ephemeral, so Kubernetes separates storage from pods. Learn emptyDir and hostPath volumes, PersistentVolumes, PersistentVolumeClaims, StorageClasses and dynamic provisioning, plus access modes and reclaim policies.
What is this?
Kubernetes separates storage lifecycle from pod lifecycle. Volumes like emptyDir and hostPath provide pod-scoped or node-scoped storage, while PersistentVolumes (PVs) and PersistentVolumeClaims (PVCs) provide cluster-level storage that survives pod recreation. A StorageClass enables dynamic provisioning: a claim triggers automatic creation of a matching PV from a provisioner such as a cloud disk or CSI driver.
Why do you need it?
- Databases and application state must survive pod and node churn.
- PVs and PVCs decouple the storage request (how much, what speed) from the storage implementation (disk type, cloud provider).
- Dynamic provisioning removes the need to pre-create volumes by hand.
- Access modes and reclaim policies make capacity and cleanup predictable.
Mental model
Reserving a warehouse unit at a storage company
A PersistentVolumeClaim
You ask for 1GiB of racked space (the PVC). The company matches you to an actual unit (the PV) with the right features. When you leave, the company either wipes and re-lets it (Delete) or holds it for you (Retain).
How it works
- A pod declares an emptyDir (pod-lifetime scratch), a hostPath (node path), or a PVC in its volumes.
- A PVC requests capacity, access mode and a StorageClass; the controller finds or provisions a matching PV.
- The PV is bound to the PVC (status Bound) and can then be mounted by pods in the claim's namespace.
- The kubelet mounts the PV at the container path declared in volumeMounts.
- When the pod is deleted the volume stays; when the PVC is deleted the reclaim policy decides what happens to the PV.
From StorageClass through PVC and PV to a mounted pod.
Key commands
kubectl get storageclassList StorageClasses with provisioner and reclaim policy.
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE standard (default) k8s.io/minikube-hostpath Delete Immediate false 7m
kubectl get pvcList claims with their status (Pending or Bound).
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE data-claim Bound pvc-3f2a1c 1Gi RWO standard 3m
kubectl get pvList cluster-level PersistentVolumes and their bound claims.
kubectl describe pvc data-claimShow claim details including capacity, access modes and events.
kubectl get pv pvc-3f2a1c -o yamlInspect the PV spec, reclaim policy and status.
kubectl delete pvc data-claimRelease the claim; the reclaim policy then decides the PV fate.
Examples
A PVC bound and mounted by a database pod
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: data-claimspec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: standard---apiVersion: v1kind: Podmetadata: name: dbspec: containers: - name: db image: postgres:16 env: - name: POSTGRES_PASSWORD value: secret volumeMounts: - name: data mountPath: /var/lib/postgresql/data volumes: - name: data persistentVolumeClaim: claimName: data-claimThe claim asks for 1GiB with ReadWriteOnce access on the standard StorageClass, which provisions a PV dynamically. The db pod mounts the claim at PostgreSQL's data directory, so the database survives pod and node failures.
In production
- Stateful databases (PostgreSQL, MySQL, Redis) persist data in PVCs so they survive pod rescheduling.
- StorageClasses map to cloud disk tiers: SSD for databases, HDD for logs.
- emptyDir is ideal for scratch space and caching that must die with the pod.
- hostPath is used by daemonsets such as log collectors that need node-local files.
Best practices
- Always use PVCs and StorageClasses for durable data; reserve emptyDir for ephemeral scratch.
- Match access modes to reality: RWO for single-node writers, RWX only when multiple nodes truly write.
- Choose reclaimPolicy Delete for disposable volumes and Retain for anything that must be inspected or recovered.
- Set storageClassName explicitly and use volumeBindingMode WaitForFirstConsumer for local or zone-aware storage.
Common mistakes
- Putting databases on emptyDir and losing data on every pod recreation.
- Using hostPath for multi-node workloads, which breaks when the pod moves nodes.
- Requesting an access mode the underlying storage cannot provide, leaving the PVC Pending.
- Forgetting that deleting a PVC with reclaimPolicy Delete destroys the volume and its data.
Common issues
PVC stuck in PendingNo StorageClass provisioner can satisfy the request, or no matching static PV exists.
Check kubectl get storageclass, set the right storageClassName, and confirm the request fits an available PV.
FailedMount: persistentvolumeclaim not foundThe pod references a claim that does not exist in its namespace.
Create the PVC in the same namespace or fix the claimName, then recreate the pod.
PV stuck in Released and the claim will not rebindreclaimPolicy Retain leaves the PV with old data and a claimRef.
Delete the PV manually (with the data preserved) or use reclaimPolicy Delete for disposable volumes.
Debugging tips
- kubectl describe pvc <name> shows why a claim is stuck Pending (no matching storage, wrong class).
- kubectl get pv -o yaml reveals reclaim policy, claimRef and status (Available/Bound/Released).
- kubectl get events --sort-by=.lastTimestamp surfaces FailedMount and provisioner errors.
- kubectl exec into the pod and df -h /mount-path to verify the volume actually mounted.
Interview checkpoint
Hands-on lab
- Inspect StorageClasses and dynamic provisioning
- Create a PVC and verify it binds to a PV
- Run a database on persistent storage
- Prove data survives pod recreation and observe reclaim behavior
Persistent Storage with PVCs
40 min ยท advanced
Quick quiz
Which access mode allows multiple nodes to read and write the same volume?
A PVC must be in the same namespace as the Pod that uses it.
Which reclaim policy immediately deletes the PersistentVolume when its claim is released?
Cheat sheet
kubectl get storageclassList storage classeskubectl get pvcList claims and statuskubectl get pvList persistent volumeskubectl describe pvc NAMEClaim details and eventskubectl get pv NAME -o yamlPV spec and reclaim policykubectl delete pvc NAMERelease the claim