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
volumeClaimTemplatesinstead.
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
volumeClaimTemplatesunder 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
Pendingbecause of storage extends the core-conceptsget→describe→logs→eventsloop:describethe 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
Pendingis a scheduling/resource problem before checking storage. Fix:describethe 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.