Skip to content

Storage

Scope: how a Pod gets a disk, and how that disk survives the Pod that used it. The workloads page covers StatefulSet's identity; this page covers the storage half of that promise — Volume, PersistentVolume, PersistentVolumeClaim, StorageClass, and the CSI driver that wires them to real storage.

TL;DR — in 30 seconds:

  • A Volume outlives a container restart but still shares the Pod's lifetime; for data that must survive the Pod being deleted/recreated, use a PersistentVolumeClaim bound to a PersistentVolume.
  • A StorageClass provisions a PV on demand (dynamic provisioning) so most teams never hand-create a PV; CSI is the standard plug between Kubernetes and a specific storage backend.
  • Access mode matters: RWO is single-node read-write (most cloud block storage) — it's why a multi-replica Deployment generally can't share one PVC for read-write data, and part of why stateful workloads reach for StatefulSet's per-replica volumeClaimTemplates instead.

1. The problem: a container's disk doesn't survive the container

A database container crashes, Kubernetes restarts it inside the same Pod — and every row it had written since the last restart is gone. A container's own filesystem is ephemeral by design: it's a writable layer on top of its image, and a fresh container gets a fresh layer. A Volume fixes that at the Pod level: it is storage attached to a Pod that outlives any single container restart inside it, because it belongs to the Pod, not the container. But a Volume still shares the Pod's lifetime — delete the Pod, and most volume types are deleted with it. That's fine for a scratch directory shared between two containers in the same Pod; it is not fine for a database's data.

Each layer in that chain outlives the one before it — a wider scope survives a narrower thing being torn down:

flowchart LR
  a["Container filesystem layer<br/>gone: container restart"] --> b["Volume (Pod-scoped)<br/>gone: Pod deleted"] --> c["PVC / PersistentVolume<br/>(cluster-scoped)<br/>survives Pod deletion"]

2. PersistentVolume and PersistentVolumeClaim — storage that outlives the Pod

For data that must survive a Pod being deleted and recreated, Kubernetes separates the supply of storage from the request for it:

Object What it represents Who creates it
PersistentVolume (PV) A real piece of storage (a cloud disk, an NFS share, …) as a cluster resource Provisioned ahead of time by an admin, or dynamically on demand (see §3)
PersistentVolumeClaim (PVC) A Pod's request for storage — "give me 10Gi, this access mode" The app author, alongside the Pod spec

A Pod never talks to a PV directly. It mounts a PVC; Kubernetes binds that PVC to a PV that satisfies its request (size, access mode, StorageClass); the Pod's data then lives in that PV independent of the Pod's own lifecycle — delete and recreate the Pod, reattach the same PVC, and the data is still there.

flowchart LR
  pod["Pod<br/>mounts a PVC"] --> pvc["PersistentVolumeClaim<br/>(request: size, access mode)"]
  pvc -- binds to --> pv["PersistentVolume<br/>(actual storage)"]
  sc["StorageClass<br/>(how to provision)"] -. provisions .-> pv

3. StorageClass and dynamic provisioning

Pre-creating a PV for every possible claim doesn't scale. A StorageClass describes how to make storage on demand — which provisioner to call and what parameters to hand it (disk type, IOPS tier, filesystem). When a PVC names a StorageClass and no matching PV already exists, the StorageClass's provisioner creates one automatically — dynamic provisioning — so most teams never hand-create a PV at all. A cluster typically has a default StorageClass, used when a PVC doesn't name one explicitly.

4. Access modes

A PV's access mode governs how many nodes can mount it at once, and how:

Access mode Meaning
ReadWriteOnce (RWO) Read-write by a single node at a time (most cloud block storage: EBS, most disks)
ReadOnlyMany (ROX) Read-only, by many nodes at once
ReadWriteMany (RWX) Read-write, by many nodes at once (needs a storage backend that supports it: NFS, EFS, …)
ReadWriteOncePod Read-write, and only a single Pod in the whole cluster — stricter than RWO, which only limits by node

RWO is the common case and the reason a Deployment with more than one replica generally can't share a single PVC for read-write data — each replica may land on a different node. That constraint is a big part of why stateful workloads reach for StatefulSet instead. Which modes a given volume actually supports depends on the backend, not on Kubernetes — the access-modes reference lists the capability per volume plugin.

5. CSI — the plug between Kubernetes and real storage

Kubernetes doesn't know how to talk to any specific storage system natively. The Container Storage Interface (CSI) is the standard plug: a CSI driver, supplied by the storage vendor or cloud provider, implements the create/attach/mount operations for its backend. StorageClasses name a CSI driver as their provisioner — swapping storage backends is mostly a matter of installing a different CSI driver and pointing StorageClasses at it, without changing how Pods or PVCs are written.

6. StatefulSet's volumeClaimTemplates

The workloads page noted a StatefulSet replica keeps "its own PersistentVolume across restarts." The mechanism is volumeClaimTemplates: instead of one PVC shared by all replicas, the StatefulSet stamps out a dedicated PVC per replica (data-app-0, data-app-1, …), each bound to its own PV. When app-1 restarts, it reattaches to the same PVC — and therefore the same data — every time, which is exactly the stable-identity-plus-storage guarantee that makes StatefulSet the right fit for databases and queues.

7. How this connects

  • StatefulSet's per-replica storage (the workloads page) is volumeClaimTemplates under the hood — this page is the "how" behind that earlier claim.
  • EBS/EFS (module 05, AWS) are the concrete backends a CSI driver on EKS provisions against — EBS maps naturally to RWO, EFS to RWX.
  • Debugging a Pod stuck Pending because of storage extends the core-concepts getdescribelogsevents loop: describe the PVC to see whether it's bound, and check for an access-mode or StorageClass mismatch between what the PVC asks for and what's available.

Common gotchas

  • Expecting a plain Volume to survive the Pod being deleted. Fix: a Volume shares the Pod's lifetime — delete the Pod and most volume types go with it; only a PersistentVolumeClaim bound to a PersistentVolume survives Pod deletion.
  • Giving a multi-replica Deployment one shared PVC for read-write data. Fix: most cloud block storage is RWO (single node, read-write at a time) — replicas on different nodes can't all mount it read-write; either use a backend/access mode that supports RWX, or move to StatefulSet's per-replica volumeClaimTemplates.
  • Assuming a Pod stuck Pending is a scheduling/resource problem before checking storage. Fix: describe the PVC too — an unbound PVC (no matching PV, no StorageClass, or a mismatched access mode) blocks scheduling just as effectively as insufficient CPU/memory.
  • Assuming every StorageClass/backend supports every access mode. Fix: access-mode support depends on the backend, not Kubernetes — check what the CSI driver/StorageClass actually offers (e.g., EBS-backed storage is typically RWO only; RWX needs a backend like EFS/NFS).
Check yourself — a database Pod restarts and its data is gone, even though you attached a Volume. What's missing?

A plain Volume only survives a container restart inside the same Pod — it shares the Pod's own lifetime, so deleting/recreating the Pod deletes most volume types with it. Data that must survive the Pod needs a PersistentVolumeClaim bound to a PersistentVolume instead.

Check yourself — why can't a 3-replica Deployment usually share one PVC for read-write data, but a 3-replica StatefulSet can give each replica its own?

Most cloud block storage is ReadWriteOnce (single node at a time), and Deployment replicas can land on different nodes, so they can't all mount the same RWO PVC read-write. A StatefulSet uses volumeClaimTemplates to stamp out a dedicated PVC per replica instead of sharing one, sidestepping the access-mode limit entirely.

You can defend this when you can explain why a Volume alone isn't enough for a database, trace how a PVC becomes a bound PV through a StorageClass, say which access mode fits a given sharing requirement, and explain what volumeClaimTemplates gives a StatefulSet that a Deployment's shared PVC can't.