Pod Volumes and Volume Lifetime
An application needs two kinds of storage: scratch space that can vanish, and data that must not. `emptyDir` and a PersistentVolumeClaim cover both, and the only thing that reliably confuses people is a claim that reports `Pending` while nothing at all is wrong with it. All three are demonstrated, including deleting the Pod and reading the file back from a new one.
Application Design and Build Guide 14 of 44 Beginner
- Kubernetes1.36.4
- Runtimecontainerd 2.2.6
- CNICalico v3.32.1
- TimeAbout 15 min
- Reviewed23 August 2026
Written against the versions above. `local-path` is this cluster's default StorageClass and uses `WaitForFirstConsumer`, so binding is deferred until a Pod is scheduled - the provisioner has to know which node to put the directory on. An `Immediate` class binds straight away, which is why the same manifest behaves differently on different clusters.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| CKA1001 | 192.168.0.175 | Ubuntu 26.04 LTS | Control Plane Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE01 | 192.168.0.176 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE02 | 192.168.0.177 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE03 | 192.168.0.178 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
Before you start
- A cluster with a default StorageClass.
- The session creates namespace
ckad-vol, a Pod with twoemptyDirvolumes, a 128Mi PVC, and two Pods that mount the claim one after the other.
-
An emptyDir lives and dies with the Pod
Two
emptyDirvolumes: one default, one withmedium: Memory.written to disk-backed written to memory-backedBoth writable, and
dfshows what they actually are - the default is the node's disk, theMemoryone is a tmpfs.Use the default for scratch space, caches and anything two containers need to share (see guide 20). Use
medium: Memorywhen the data must never touch disk, remembering it counts against the container's memory limit - a tmpfs full of files can get youOOMKilled.Both are deleted with the Pod. Not with the container - a container restart keeps the emptyDir - but with the Pod.
bash Example session kubectl create namespace ckad-volnamespace/ckad-vol createdkubectl -n ckad-vol wait --for=condition=Ready pod/scratch --timeout=120spod/scratch condition metkubectl -n ckad-vol exec scratch -- sh -c 'echo "written to disk-backed" > /scratch/a.txt; echo "written to memory-backed" > /inmem/b.txt; cat /scratch/a.txt /inmem/b.txt'written to disk-backedwritten to memory-backedkubectl -n ckad-vol exec scratch -- sh -c 'df -h /scratch /inmem | grep -vE "^Filesystem"'/dev/mapper/ubuntu--vg-ubuntu--lv 47.1G 19.9G 25.0G 44% /scratchtmpfs 16.0M 4.0K 16.0M 0% /inmemExpected resultBoth volumes writable, one on disk and one on tmpfs.
Success conditionYou can pick between the two ephemeral options and know what each costs.
-
A claim that waits on purpose
A PVC with no Pod using it:
NAME STATUS VOLUME CAPACITY STORAGECLASS data Pending local-pathPending, and nothing is wrong. The reason is in the StorageClass:NAME MODE local-path WaitForFirstConsumerThe provisioner creates the volume on a specific node, so it cannot act until the scheduler has decided which node the Pod goes to. Binding is deliberately deferred - if it bound immediately, it might pick a node the Pod cannot be scheduled to and deadlock.
This is worth recognising cold, because the instinct is to start debugging storage. The distinction:
Pending+WaitForFirstConsumer+ no Pod yet -> normal, create the Pod.Pendingwith a Pod already mounting it -> a real problem.kubectl describe pvcwill have events.
bash Example session sleep 10; kubectl -n ckad-vol get pvc dataNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGEdata Pending local-path <unset> 10skubectl get storageclass local-path -o 'custom-columns=NAME:.metadata.name,MODE:.volumeBindingMode'NAME MODElocal-path WaitForFirstConsumerExpected resultA
Pendingclaim and aWaitForFirstConsumerclass.Success conditionYou will not debug a PVC that is behaving correctly.
-
A Pod binds it
The moment a Pod mounting the claim is scheduled:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS data Bound pvc-3316a118-094b-4be5-9221-714fe45610f3 128Mi RWO local-pathBound, with a PersistentVolume created automatically and named after the claim's UID. That is dynamic provisioning - you never wrote a PV.survives the podNote
ACCESS MODES: RWO- ReadWriteOnce, meaning it can be mounted read-write by one node at a time. It is the default and the only mode most provisioners support, and it is why a Deployment with two replicas and one RWO claim will have one Pod stuck if the replicas land on different nodes.bash Example session kubectl -n ckad-vol wait --for=condition=Ready pod/writer --timeout=180spod/writer condition metkubectl -n ckad-vol get pvc dataNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGEdata Bound pvc-3316a118-094b-4be5-9221-714fe45610f3 128Mi RWO local-path <unset> 15skubectl -n ckad-vol exec writer -- sh -c 'echo "survives the pod" > /data/keep.txt; cat /data/keep.txt'survives the podkubectl -n ckad-vol get pod writer -o 'custom-columns=POD:.metadata.name,NODE:.spec.nodeName'POD NODEwriter cka1001-node01Expected resultThe claim
Boundand a file written to it.Success conditionYou have data in a volume the Pod does not own.
-
Delete the Pod and look again
The Pod is deleted. The claim is not:
NAME STATUS VOLUME CAPACITY data Bound pvc-3316a118-... 128Miand a completely new Pod mounting the same claim reads the file back:
survives the podThat is the whole point. The lifecycle of a PVC is tied to the namespace, not to any Pod. Delete the workload, redeploy it, roll it forward - the data stays.
What eventually removes it is deleting the claim, at which point the StorageClass's
reclaimPolicydecides.local-pathhere isDelete, so the volume and its data go with the claim.Retainwould keep the PV and its data behind for manual recovery.bash Example session kubectl -n ckad-vol delete pod writer --wait=truepod "writer" deleted from ckad-vol namespacekubectl -n ckad-vol get pvc dataNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGEdata Bound pvc-3316a118-094b-4be5-9221-714fe45610f3 128Mi RWO local-path <unset> 47skubectl -n ckad-vol wait --for=condition=Ready pod/reader --timeout=180spod/reader condition metkubectl -n ckad-vol exec reader -- cat /data/keep.txtsurvives the podkubectl delete namespace ckad-vol --wait=falsenamespace "ckad-vol" deletedExpected resultThe claim survives the Pod and the file is still readable.
Success conditionYou can prove data outlives the Pod that wrote it.
Troubleshooting
A PVC is
Pendingand there are no events.Why:
WaitForFirstConsumerwith no Pod mounting it yet.Fix:Normal. Create the Pod; the claim binds when the Pod is scheduled.
A PVC is
Pendingwithno persistent volumes available.Why: No default StorageClass, or a
storageClassNamethat does not exist.Fix:
kubectl get storageclassand look for(default).A second replica cannot start - volume already attached.
Why:
ReadWriteOnceallows one node at a time.Fix:Use a StatefulSet with
volumeClaimTemplatesfor a claim per replica, or a provisioner that supportsReadWriteMany.A memory-backed emptyDir got the container OOM-killed.
Why:
medium: Memorycounts against the container's memory limit.Fix:Set
sizeLimiton the volume and size the memory limit to include it. See guide 52.