intermediate~4h

Storage: Volumes, PVs, PVCs & StorageClass

A container's own filesystem is ephemeral. Kubernetes Volumes solve this at increasing levels of durability: emptyDir (Pod-lifetime), PersistentVolume/PVC (cluster-scoped, outlives Pods), and StorageC

A container's own filesystem is ephemeral. Kubernetes Volumes solve this at increasing levels of durability: emptyDir (Pod-lifetime), PersistentVolume/PVC (cluster-scoped, outlives Pods), and StorageClass (dynamic provisioning via CSI driver).

  • emptyDir: created when Pod is scheduled, deleted when Pod is removed. Useful for scratch space or sharing files between containers.

  • A PersistentVolume (PV) represents actual storage infrastructure as a cluster-level object with its own lifecycle.

  • A PVC is an application's request for storage (size, access mode) -- Kubernetes binds a matching PV.

  • Access modes: ReadWriteOnce (one NODE at a time, not one Pod), ReadOnlyMany, ReadWriteMany, ReadWriteOncePod.

  • StorageClass enables dynamic provisioning: when a PVC references a StorageClass and no PV exists, the CSI driver creates one.

  • Decide the durability tier needed: emptyDir for Pod-lifetime scratch space, or PersistentVolume/PVC for data that must outlive the Pod.

  • For dynamic provisioning, define or pick a StorageClass (specifies the CSI provisioner, e.g. cloud block storage, and parameters like volume type).

  • Create a PVC requesting size and accessMode; if it references a StorageClass and no matching PV exists, the CSI driver provisions one automatically and binds it.

  • Reference the PVC in the Pod spec's volumes, then mount it into the container via volumeMounts.

  • For StatefulSets, use volumeClaimTemplates instead of a single shared PVC, so each replica gets its own dynamically-provisioned volume.

  • Set the StorageClass's volumeBindingMode: WaitForFirstConsumer when using zonal storage, so the PV is provisioned in the same zone the Pod actually gets scheduled to, not an arbitrary one.

  • A database Pod using a dynamically-provisioned PVC backed by cloud block storage (e.g. EBS, Persistent Disk) via a StorageClass, so data survives Pod rescheduling.

  • Two containers in the same Pod sharing files through an emptyDir volume (e.g. a sidecar processing files an app container writes).

  • A CI build Pod using emptyDir with sizeLimit as scratch space for build artifacts, cleaned up automatically when the Pod exits.

  • A shared team file store using ReadWriteMany-capable storage (like an NFS-backed or cloud file-storage CSI driver) mounted by multiple Pods simultaneously.

  • Multi-zone clusters using WaitForFirstConsumer binding so a Pod and its storage always land in the same availability zone, avoiding cross-zone attach failures.

  • Use WaitForFirstConsumer as the default volumeBindingMode for any StorageClass backing zonal cloud storage -- Immediate binding can provision a volume in a zone the Pod is never scheduled to.

  • Set accessModes deliberately based on actual need -- defaulting to ReadWriteMany when ReadWriteOnce would do limits which storage backends are even viable.

  • Set sizeLimit on emptyDir for anything that could grow unbounded (build caches, logs) to avoid a single Pod exhausting node disk.

  • Use a StorageClass with allowVolumeExpansion: true if you expect PVC sizes to need to grow later -- resizing without it requires manual volume migration.

  • Set persistentVolumeReclaimPolicy: Retain for data you cannot afford to lose to an accidental PVC deletion, rather than the default Delete some provisioners use.

  • Assuming ReadWriteOnce means one Pod at a time -- it actually means one NODE at a time, so multiple Pods on the same node CAN mount the same RWO volume simultaneously.

  • Using emptyDir for data that actually needs to survive Pod restarts -- it's deleted the moment the Pod is removed, not just restarted within limits.

  • Forgetting that a PVC's StorageClass determines binding mode at PVC-creation time -- switching StorageClasses later requires creating a new PVC and migrating data, not editing the existing one.

  • Deleting a PVC without checking persistentVolumeReclaimPolicy first, then being surprised when the underlying cloud disk is gone (Delete policy) rather than retained.

  • Requesting ReadWriteMany without confirming the underlying CSI driver actually supports it -- many common cloud block-storage drivers only support ReadWriteOnce.

  • Not setting a StorageClass at all and relying on the cluster's default, which may have cost or performance characteristics unsuited to the workload.

  • Volume provisioning is not instant -- dynamic provisioning of cloud block storage typically takes seconds to tens of seconds; WaitForFirstConsumer binding avoids double-provisioning delay from a mis-scheduled volume needing to be recreated.

  • Choose StorageClass parameters (volume type, IOPS/throughput tier) matching actual workload I/O patterns -- a database on a low-IOPS StorageClass will bottleneck long before CPU/memory limits are reached.

  • Attach/detach operations for network-backed block storage have real latency on Pod rescheduling -- factor this into failover time expectations, it's not instantaneous like a local disk.

  • emptyDir backed by tmpfs (medium: Memory) is dramatically faster than disk-backed emptyDir for high-churn scratch data, at the cost of consuming node RAM instead of disk.

  • Take regular volume snapshots (via the CSI VolumeSnapshot API, if the driver supports it) independent of application-level backups for stateful workloads' PVCs.

  • Set persistentVolumeReclaimPolicy: Retain plus a documented cleanup process for production data volumes, rather than relying on the provisioner's default Delete behavior.

  • Monitor PVC capacity utilization and alert before a volume fills up -- a full volume causes application-level write failures that don't always surface as an obvious Kubernetes-level event.

  • Standardize on a small number of StorageClasses per cluster (e.g. fast-ssd, standard) rather than letting every team define bespoke ones.

  • Create a StorageClass, a PVC referencing it, and a Pod mounting the PVC; confirm dynamic provisioning by checking kubectl get pv for the auto-created PV.

  • Delete the Pod (not the PVC) and recreate it, confirming the same PVC/PV reattaches with data intact.

  • Create two containers in one Pod sharing an emptyDir volume -- write a file from one container and read it from the other.

  • Set volumeBindingMode: WaitForFirstConsumer on a StorageClass and observe (via kubectl get pvc) that the PVC stays Pending until a Pod referencing it is actually scheduled.

  • emptyDir is Pod-lifetime storage; PVs and PVCs provide storage that outlives Pods.

  • PVCs are infrastructure-agnostic requests; StorageClasses define how to dynamically provision matching PVs.

  • ReadWriteOnce restricts to one NODE, not one Pod -- a frequently misunderstood distinction.

  • reclaimPolicy: Retain is safer for important data; Delete is the often-surprising default.

Want a visual for this concept?

Generate a diagram tailored to “Storage: Volumes, PVs, PVCs & StorageClass” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Scaling: HPA, VPA & Cluster Autoscaler← Back to all Kubernetes chapters