← Back

2026

Kubernetes Storage: PV, PVC and StorageClass

Pod storage resets on every restart. PersistentVolumes, PersistentVolumeClaims and StorageClasses are how Kubernetes separates the concern of providing storage from the concern of requesting it, making stateful workloads possible without tying workloads to specific infrastructure.

Most Kubernetes topics eventually come with a moment where something clicks and the whole model makes sense at once. Storage is not like that. It has three separate abstractions layered on top of each other, each one solving a different problem and it is easy to get through weeks of cluster work without ever needing to think about them carefully. Then you need to run a database or a stateful service and suddenly all three matter at once.

The core issue is that Pod storage is ephemeral by default. When a container restarts, its filesystem resets to what was in the image. When a Pod is rescheduled to a different node, anything written to local disk on the previous node is gone. For stateless workloads this is fine. For anything that needs to remember things across restarts, you need a different approach.

PersistentVolumes, PersistentVolumeClaims and StorageClasses are Kubernetes's answer to that problem. They separate the concern of providing storage from the concern of requesting it and they make provisioning repeatable rather than manual. Understanding how the three fit together makes the rest of stateful workload management considerably less confusing.

The three pieces

Each concept handles a different layer of the problem. The PV is the actual storage resource. The PVC is the request for storage. The StorageClass is what allows the cluster to create PVs automatically instead of waiting for an administrator to create them by hand.

PersistentVolume (PV)

A PersistentVolume is a piece of storage that has been provisioned in the cluster. It exists independently of any Pod. An administrator creates it ahead of time, pointing at something real: an NFS share, a cloud disk, a local directory on a node. The PV describes capacity, access modes and which storage system backs it. It has its own lifecycle that is not tied to any workload.

PersistentVolumeClaim (PVC)

A PersistentVolumeClaim is a request for storage made by a workload. It says how much space is needed and what access mode is required. Kubernetes then finds a PV that satisfies those requirements and binds the two together. The Pod references the PVC, not the PV directly. This separation means the workload does not need to know anything about the underlying storage system.

StorageClass

A StorageClass tells Kubernetes how to provision a PersistentVolume on demand. Instead of creating PVs by hand, you define a class with a provisioner and parameters. When a PVC references that class, the cluster creates a matching PV automatically. StorageClasses are what make dynamic provisioning possible. Most cloud-managed clusters ship with at least one default class already configured.

How binding works

When a PVC is created, the control plane searches for a PV that satisfies it. The match is based on capacity, access mode and StorageClass. If a suitable PV exists, it gets bound to the claim. If not and a StorageClass is referenced, the provisioner creates one. Once bound, the PV and PVC are locked together until the PVC is deleted. No other claim can take that PV even if it has leftover capacity.

PersistentVolumeClaim — requesting storage dynamically

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

name: postgres-data

namespace: production

spec:

accessModes:

- ReadWriteOnce

storageClassName: fast-ssd

resources:

requests:

storage: 20Gi

The Pod references the PVC by name inside its volumes block and then mounts that volume into a container path. The container sees a regular filesystem directory. It does not know whether the storage is backed by a cloud disk, an NFS share, or anything else. That detail stays in the PV.

Pod spec — mounting the PVC

spec:

containers:

- name: postgres

image: postgres:16

volumeMounts:

- mountPath: /var/lib/postgresql/data

name: data

volumes:

- name: data

persistentVolumeClaim:

claimName: postgres-data

Access modes

Access modes describe how a volume can be mounted across nodes. The mode you can actually use depends on what the underlying storage backend supports. A PV advertises which modes it is capable of. The PVC requests the mode it needs. They must agree for binding to happen.

ReadWriteOnce

RWO

The volume can be mounted read-write by a single node at a time. This is the most common mode. It works well for databases, queues and anything stateful that runs on one node at a time. Multiple Pods on the same node can still use it.

ReadOnlyMany

ROX

The volume can be mounted read-only by many nodes simultaneously. Useful for shared configuration, reference datasets or static content that needs to be available across many replicas without being modified.

ReadWriteMany

RWX

The volume can be mounted read-write by many nodes at the same time. Not all storage backends support this. NFS and some cloud file systems do. Block storage like EBS generally does not. When multiple Pods need to write to the same volume concurrently, this is the mode you need.

ReadWriteOncePod

RWOP

The volume can be mounted read-write by exactly one Pod across the entire cluster. Introduced to cover cases where even RWO was not strict enough because it allowed multiple Pods on the same node to share the volume.

StorageClass in practice

A StorageClass is what connects a PVC to a specific provisioner with specific settings. You define it once and reference it by name from any number of claims. Different classes can represent different performance tiers, different replication policies, or storage in different availability zones. Teams often set up a handful of classes to give workloads meaningful choices without exposing the underlying cloud configuration.

StorageClass — AWS EBS with gp3

apiVersion: storage.k8s.io/v1

kind: StorageClass

metadata:

name: fast-ssd

annotations:

storageclass.kubernetes.io/is-default-class: "false"

provisioner: ebs.csi.aws.com

parameters:

type: gp3

iops: "4000"

throughput: "200"

encrypted: "true"

reclaimPolicy: Delete

allowVolumeExpansion: true

volumeBindingMode: WaitForFirstConsumer

The volumeBindingMode: WaitForFirstConsumer setting is worth paying attention to. With the default Immediate mode, the PV is provisioned the moment the PVC is created. On cloud providers where disks are tied to an availability zone, this can create a disk in the wrong zone before the scheduler has decided where the Pod will run. WaitForFirstConsumer delays provisioning until a Pod is actually being scheduled, so the disk ends up in the correct zone.

Reclaim policies

When a PVC is deleted, the reclaim policy on the PV determines what happens to the underlying storage. Getting this wrong is how data disappears unexpectedly.

Retain

When the PVC is deleted, the PV is not touched. It moves to a Released state and sits there until an administrator manually decides what to do with the underlying data. The data is preserved. Safe for production workloads where accidental deletion would be a serious problem.

Delete

When the PVC is deleted, both the PV and the underlying storage resource are deleted automatically. Common with dynamically provisioned volumes where the lifecycle of the data should match the lifecycle of the claim. Convenient for ephemeral workloads but carries real risk if used carelessly.

Recycle

Deprecated. The volume was scrubbed with a basic rm command and made available again. Replaced by dynamic provisioning in practice. You will still see it mentioned in older documentation but should not use it for new workloads.

For production databases, use Retain. The extra step of manually releasing the PV is worth it. Accidental deletion of a claim should not be the same as accidental deletion of the data inside it.

StatefulSets and volumeClaimTemplates

Running a single Pod with a PVC is straightforward enough. Scaling that to multiple replicas where each one needs its own dedicated volume is a different problem. StatefulSets solve it with volumeClaimTemplates.

A StatefulSet with a volumeClaimTemplate creates one PVC per replica, named after the template with the Pod index appended. A StatefulSet named postgres with three replicas creates data-postgres-0, data-postgres-1 and data-postgres-2. Each Pod always gets the same PVC when it is rescheduled. That is what makes StatefulSets suitable for clustered databases: identity and storage are both stable.

StatefulSet — volumeClaimTemplates

spec:

replicas: 3

volumeClaimTemplates:

- metadata:

name: data

spec:

accessModes: [ReadWriteOnce]

storageClassName: fast-ssd

resources:

requests:

storage: 50Gi

One important thing to be aware of: when you delete a StatefulSet, its PVCs are not deleted with it. This is deliberate. Kubernetes assumes you probably want to keep the data even if the workload definition is gone. Cleaning up the PVCs is a separate manual step.

Things that go wrong

Storage problems tend to surface as Pods stuck in Pending or containers that start up but fail to find the data they expect. Most issues come down to a mismatch somewhere in the binding chain.

PVC stuck in Pending

The claim sits in Pending indefinitely because no PV matches its requirements. The capacity might be slightly too small, the access mode might not match what any available PV offers, or the StorageClass name could be wrong. Describing the PVC shows the binding failure reason. If dynamic provisioning is expected, check that the StorageClass provisioner is actually running in the cluster.

Deleting a namespace with active PVCs

Namespaces with PVCs that have a Retain policy will hang in Terminating. The finalizer on the PVC prevents deletion until the volume is manually released. The fix is to patch out the finalizer after confirming the data has been handled. This catches people off guard the first time they try to tear down a namespace quickly.

Assuming ReadWriteMany works everywhere

RWX is listed as a valid access mode but most block storage backends simply do not implement it. Creating a PVC with RWX against an EBS-backed StorageClass will leave it Pending forever. The provisioner will create the volume but the mount will fail once two nodes try to attach it. Always verify what access modes the chosen StorageClass actually supports.

Volume size cannot be reduced

Kubernetes supports expanding PVCs when the StorageClass has allowVolumeExpansion set to true. It does not support shrinking them. Requesting a smaller size is silently ignored. When a volume is genuinely too large, the only path is to provision a new one and migrate the data.

Where it fits

PVs, PVCs and StorageClasses sit underneath stateful workloads in the Kubernetes stack. Anything that needs to write data that survives a restart will end up here: databases, message queues, object stores, anything that keeps state. The abstraction exists so that the team writing the workload does not need to know which cloud provider the cluster is running on. The claim describes what is needed. The StorageClass and provisioner handle the rest.

StatefulSets are the workload type most directly tied to this system. They depend on stable storage identity to work correctly. Deployments can use PVCs too, but because Deployments can schedule multiple Pods on different nodes simultaneously, they are usually limited to ReadWriteMany volumes, which rules out most block storage options. When a Deployment needs persistent storage in a real production environment, teams often end up reconsidering whether a StatefulSet was actually the right choice to begin with.

The CSI layer is worth knowing about as your cluster grows more complex. The Container Storage Interface is the plugin system that allows storage vendors to write drivers for Kubernetes without modifying Kubernetes itself. Every major cloud provider ships a CSI driver for their block storage. Third-party storage systems like Ceph, Longhorn and Rook also integrate through CSI. When you define a StorageClass, you are referencing one of these drivers by name. That driver is responsible for the actual provisioning, attachment and cleanup of volumes.