🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

What Is a Kubernetes PersistentVolume and PersistentVolumeClaim? Explained Simply

Kubernetes PersistentVolume (PV) and PersistentVolumeClaim (PVC) explained for beginners — what they are, how pods use them for storage, the binding lifecycle, StorageClass dynamic provisioning, and common patterns.

Shubham4 min read
Share:Tweet

Kubernetes pods are ephemeral — when a pod restarts, everything written to its filesystem is gone. For applications that need data to survive pod restarts (databases, file uploads, logs), Kubernetes provides persistent storage through PersistentVolumes.

The way this works involves two objects that often confuse beginners: PersistentVolume (PV) and PersistentVolumeClaim (PVC). Here is what they are and how they work together.

The Apartment Analogy

Think of it this way:

  • PersistentVolume (PV) = an apartment that exists and is available to rent. It has specific characteristics: size, location, features.
  • PersistentVolumeClaim (PVC) = a rental application where you specify what you need: "I need a 2-bedroom apartment in downtown."
  • Binding = the process of matching a suitable apartment to a rental application.

The tenant (your pod) does not directly care about which specific apartment they get — they just need an apartment that meets their requirements. The pod only interacts with the PVC (the claim), not the PV directly.

PersistentVolume (PV)

A PV represents a piece of storage in the cluster — an actual disk or storage system. It can be:

  • An AWS EBS volume
  • A Google Persistent Disk
  • An NFS share
  • A local disk on a node
  • Azure Disk or Azure Files
yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: manual-pv-100gi
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteOnce    # Can only be mounted by one node at a time
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  awsElasticBlockStore:
    volumeID: vol-0a1b2c3d4e5f6g7h8
    fsType: ext4

In practice, you rarely create PVs manually anymore — StorageClasses handle this automatically (dynamic provisioning). But understanding the PV object helps you understand what exists.

PersistentVolumeClaim (PVC)

A PVC is a request for storage. Your pod does not talk to the storage directly — it references a PVC, and Kubernetes handles binding the PVC to an appropriate PV.

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-database-storage
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce     # Must match PV's access mode
  storageClassName: gp3  # Which StorageClass to use
  resources:
    requests:
      storage: 20Gi     # How much storage you need

Access Modes

Access modes define how the volume can be mounted:

ModeShortMeaning
ReadWriteOnceRWOOne node can read/write. Most common for databases.
ReadOnlyManyROXMany nodes can read (no writes). Good for shared config.
ReadWriteManyRWXMany nodes can read/write. Requires shared storage like NFS or EFS.

EBS volumes (and most block storage) only support ReadWriteOnce. If you need ReadWriteMany, you need shared storage — AWS EFS, NFS, or Ceph.

Using a PVC in a Pod

Once you have a PVC, mount it in your pod like this:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: postgres
spec:
  containers:
  - name: postgres
    image: postgres:16
    env:
    - name: POSTGRES_PASSWORD
      value: "changeme"
    volumeMounts:
    - name: postgres-data          # References the volume name below
      mountPath: /var/lib/postgresql/data
  volumes:
  - name: postgres-data
    persistentVolumeClaim:
      claimName: my-database-storage  # References the PVC

The pod mounts the PVC at /var/lib/postgresql/data. Data written there survives pod restarts — PostgreSQL's data files persist on the actual disk.

StorageClass and Dynamic Provisioning

Manually creating PVs is tedious. StorageClasses automate this — when a PVC is created with a StorageClass, Kubernetes automatically provisions a PV that meets the claim's requirements.

yaml
# StorageClass definition (usually pre-installed in managed clusters)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
provisioner: ebs.csi.aws.com  # The CSI driver that creates EBS volumes
parameters:
  type: gp3
  encrypted: "true"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

When you create a PVC that references storageClassName: gp3, the EBS CSI driver automatically:

  1. Creates an EBS gp3 volume with the requested size
  2. Creates a PV representing that volume
  3. Binds the PV to your PVC

You never write any YAML for the PV — it appears automatically.

bash
# Watch the binding happen
kubectl get pvc my-database-storage -n production -w
 
# NAME                  STATUS    VOLUME                CAPACITY
# my-database-storage   Pending   -                     -         (waiting for pod)
# my-database-storage   Bound     pvc-abc123def456       20Gi      (after pod is scheduled)

The PVC Lifecycle

PVC Created (Pending)
    ↓
StorageClass provisions a PV automatically
    ↓
PVC Bound to PV
    ↓
Pod mounts PVC
    ↓
Pod deleted → PVC and PV remain (data safe)
    ↓
PVC deleted → depends on reclaimPolicy:
    - Delete: PV deleted, EBS volume deleted (data gone)
    - Retain: PV stays (data preserved), must be manually reclaimed

reclaimPolicy: Retain is safer for production — if you accidentally delete a PVC, the underlying EBS volume is not deleted automatically. You have to clean it up manually but at least you have a chance to recover data.

Common Patterns

Database with StatefulSet (recommended):

yaml
# StatefulSet automatically creates PVCs per pod
volumeClaimTemplates:
- metadata:
    name: data
  spec:
    accessModes: ["ReadWriteOnce"]
    storageClassName: gp3
    resources:
      requests:
        storage: 100Gi

Each StatefulSet pod gets its own PVC (data-postgres-0, data-postgres-1) that sticks to that pod permanently.

Shared configuration (ReadOnlyMany with NFS/EFS):

yaml
# For configuration files shared across pods
spec:
  accessModes:
    - ReadOnlyMany
  storageClassName: efs-sc  # AWS EFS StorageClass
  resources:
    requests:
      storage: 1Gi  # EFS ignores this — it is elastic

Key Commands

bash
# List PVCs in a namespace
kubectl get pvc -n production
 
# Check PVC status and which PV it is bound to
kubectl describe pvc my-database-storage -n production
 
# List PVs in the cluster
kubectl get pv
 
# See which PVC a PV is bound to
kubectl get pv pvc-abc123def456

Ready for more storage topics? Read our Kubernetes PVC stuck in Pending fix and Kubernetes StatefulSet vs Deployment guide.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments