Home DevOps Kubernetes Persistent Volume Claims: Dynamic Provisioning That Won't Fail at 3 AM
Intermediate 3 min · July 18, 2026

Kubernetes Persistent Volume Claims: Dynamic Provisioning That Won't Fail at 3 AM

Dynamic provisioning in Kubernetes: avoid PVC failures with StorageClass, reclaim policies, and production patterns that survive node failures and cloud outages..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
Production
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 25 min
  • Basic Kubernetes pod and deployment YAML syntax
  • Familiarity with kubectl commands
  • Understanding of persistent volumes (PV) and persistent volume claims (PVC) concepts
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

Dynamic provisioning creates PVs on demand via a StorageClass. Define a StorageClass with a provisioner (e.g., kubernetes.io/aws-ebs), then submit a PVC referencing that StorageClass. Kubernetes calls the provisioner to create the actual storage volume and binds it to the PVC automatically.

✦ Definition~90s read
What is Kubernetes Persistent Volume Claims?

Dynamic provisioning in Kubernetes automatically creates persistent volumes (PVs) when a persistent volume claim (PVC) is submitted, using a StorageClass to define the backend storage provider (e.g., AWS EBS, GCE PD, NFS). It eliminates the need to pre-provision PVs manually.

Imagine you run a co-working space.
Plain-English First

Imagine you run a co-working space. Instead of building desks in advance (static provisioning), you have a magic desk factory (StorageClass). When someone asks for a desk (PVC), the factory instantly builds one to their specs (size, speed) and delivers it. If they leave, the factory can either reclaim the desk or recycle it, depending on your policy.

You've seen it: a pod stuck in Pending for hours because someone forgot to pre-create a PV. Or worse, a statefulset that silently loses data when a node dies because the volume was on local SSD with no replication. Static provisioning is a ticking time bomb. Dynamic provisioning is the fix — but only if you understand the trade-offs. By the end of this, you'll design StorageClasses that survive node failures, cloud outages, and your own mistakes.

Why Static Provisioning Is a Production Liability

Before dynamic provisioning, you had to create PVs manually — one per claim. That meant either over-provisioning (wasting cloud spend) or under-provisioning (blocking deployments). In a CI/CD pipeline, every new environment required a human to run kubectl create pv or a script that often failed because the storage backend was out of quota. Dynamic provisioning solves this by making the cluster request storage on demand, just like pods request CPU. The key enabler is the StorageClass, which tells Kubernetes which provisioner to call and what parameters to pass (e.g., disk type, IOPS, replication).

storageclass-aws-ebs.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# io.thecodeforge — DevOps tutorial
# StorageClass for AWS EBS gp3 volumes with dynamic provisioning
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com  # AWS EBS CSI driver
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
reclaimPolicy: Delete  # Auto-delete PV and volume when PVC is deleted
volumeBindingMode: WaitForFirstConsumer  # Delay PV creation until pod is scheduled
allowVolumeExpansion: true
Output
storageclass.storage.k8s.io/fast-ssd created
⚠ Production Trap: Wrong Provisioner Name
Using kubernetes.io/aws-ebs (in-tree) instead of ebs.csi.aws.com (CSI) will work on older clusters but is deprecated. CSI drivers offer better features like volume expansion and snapshots. Always use the CSI driver if available.

Designing a StorageClass That Matches Your Workload

Not all storage is equal. A MySQL database needs high IOPS and low latency. A log aggregator needs cheap, large capacity. A shared filesystem needs ReadWriteMany. Your StorageClass should encode these requirements. The parameters field is provisioner-specific — read the docs. For AWS EBS, you can set type (gp3, io1, st1), iops, throughput, and encrypted. For GCE PD, you set type (pd-ssd, pd-standard) and replication-type. For NFS, you set server and path. The volumeBindingMode matters: Immediate creates the PV as soon as the PVC is submitted, even if no pod needs it yet. WaitForFirstConsumer delays creation until a pod is scheduled, which respects topology constraints (e.g., zone affinity). Use WaitForFirstConsumer for stateful workloads to avoid orphaned volumes in wrong zones.

pvc-mysql.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# io.thecodeforge — DevOps tutorial
# PVC for MySQL database using fast-ssd StorageClass
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data
spec:
  accessModes:
    - ReadWriteOnce  # Only one pod can write at a time
  volumeMode: Filesystem
  resources:
    requests:
      storage: 100Gi
  storageClassName: fast-ssd  # Must match StorageClass name
Output
persistentvolumeclaim/mysql-data created
StorageClass Selection Decision Tree
IfWorkload requires low latency and high IOPS (database)
UseUse SSD-based StorageClass (e.g., gp3, io1, pd-ssd) with WaitForFirstConsumer
IfWorkload is throughput-heavy (logs, backups)
UseUse HDD-based StorageClass (e.g., st1, sc1, pd-standard) with Immediate binding
IfMultiple pods need concurrent access to same volume
UseUse ReadWriteMany-capable StorageClass (e.g., NFS, EFS, Azure Files)

Reclaim Policies: Delete vs Retain — Choose Wisely

The reclaimPolicy determines what happens to the PV and underlying storage when the PVC is deleted. Delete removes both — your cloud bill stays clean. Retain leaves the PV in Released state and the storage volume intact. You must manually delete the PV and then the cloud resource. Why would you ever use Retain? When the data is irreplaceable — a production database that must survive accidental PVC deletion. But Retain means you're on the hook for cleanup. I've seen teams burn thousands of dollars on orphaned EBS volumes because they forgot to clean up Retain PVs. My rule: use Delete for ephemeral environments and stateless apps. Use Retain only for stateful workloads with a backup strategy. And always set a StorageClass with Delete as default.

storageclass-retain.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
# io.thecodeforge — DevOps tutorial
# StorageClass with Retain policy for critical data
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: critical-data
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
reclaimPolicy: Retain  # PV and volume survive PVC deletion
volumeBindingMode: WaitForFirstConsumer
Output
storageclass.storage.k8s.io/critical-data created
💡Senior Shortcut: Default StorageClass
Set a default StorageClass by adding annotation storageclass.kubernetes.io/is-default-class: "true". Then any PVC without storageClassName uses it. This prevents accidental use of the cluster's built-in (often slow) default.

Volume Expansion: Growing Without Downtime

You will run out of disk space. It's not if, but when. Dynamic provisioning supports volume expansion if the StorageClass has allowVolumeExpansion: true and the CSI driver supports it. To expand, edit the PVC's spec.resources.requests.storage to a larger value. Kubernetes will call the provisioner to resize the underlying volume. The pod must be using the volume — if the filesystem supports online resize (most do), the new space is available immediately. If not, you may need to restart the pod. Always test expansion in staging. I've seen a resized volume fail because the underlying storage backend had a quota limit. Monitor kubectl describe pvc for events like Waiting for user to (re-)start a pod to finish file system resize of the volume.

expand-pvc.shBASH
1
2
3
4
5
# io.thecodeforge — DevOps tutorial
# Expand PVC from 100Gi to 200Gi
kubectl patch pvc mysql-data -p '{"spec": {"resources": {"requests": {"storage": "200Gi"}}}}'
# Watch resize progress
kubectl get pvc mysql-data -w
Output
persistentvolumeclaim/mysql-data patched
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-data Bound pvc-abc123 200Gi RWO fast-ssd 5m
⚠ Never Do This: Shrinking a PVC

Topology Constraints: Keeping Volumes Close to Pods

Cloud storage volumes are zonal — an EBS volume in us-east-1a cannot be attached to a pod in us-east-1b. If your pod gets scheduled in a different zone, the volume will fail to attach. The fix is volumeBindingMode: WaitForFirstConsumer combined with allowedTopologies in the StorageClass. This tells Kubernetes to delay PV creation until a pod is scheduled, then create the PV in the same zone as the pod. Without this, you risk Multi-Attach errors or scheduling failures. For multi-zone clusters, always use WaitForFirstConsumer for stateful workloads. For stateless workloads, use Immediate and accept that the PV may be in a different zone — but then your pod must be scheduled in that zone (use nodeSelector or affinity).

storageclass-topology.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# io.thecodeforge — DevOps tutorial
# StorageClass with zone-aware provisioning
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: zone-aware-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
  - key: topology.ebs.csi.aws.com/zone
    values:
    - us-east-1a
    - us-east-1b
Output
storageclass.storage.k8s.io/zone-aware-ssd created
🔥Interview Gold: WaitForFirstConsumer vs Immediate
Interviewers love this. Immediate creates PV on PVC creation, ignoring pod topology. WaitForFirstConsumer defers PV creation until a pod using the PVC is scheduled, then creates the PV in the same zone. Use WaitForFirstConsumer for stateful workloads in multi-zone clusters to avoid cross-zone attachment failures.

When Dynamic Provisioning Is Overkill

Dynamic provisioning adds complexity: you need a CSI driver, IAM permissions, and StorageClass definitions. For a single-node cluster (e.g., Minikube, Docker Desktop), static provisioning with hostPath is simpler and faster. For development environments where data loss is acceptable, use emptyDir instead of PVCs. For production, dynamic provisioning is almost always the right choice — but don't use it for temporary scratch space. Also, if your storage backend doesn't support dynamic provisioning (e.g., some on-premise NFS servers), you're stuck with static PVs. In that case, use a manual PV with a nodeSelector to pin pods to the node with the NFS mount.

static-pv-nfs.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# io.thecodeforge — DevOps tutorial
# Static PV for NFS when dynamic provisioning is not available
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.1.100
    path: /exports/data
  persistentVolumeReclaimPolicy: Retain
Output
persistentvolume/nfs-pv created
💡Senior Shortcut: Use CSI Driver for NFS
If your NFS server supports it, use the NFS CSI driver (nfs.csi.k8s.io) for dynamic provisioning. It creates subdirectories per PVC automatically, avoiding manual PV management.

Common Mistakes That Burn Production

I've seen these mistakes repeatedly. First: forgetting to set storageClassName in the PVC, which uses the cluster's default StorageClass — often the slow, generic one. Always specify it explicitly. Second: using ReadWriteOnce for a deployment with multiple replicas — only one pod can write at a time, others will fail. Use ReadWriteMany only if the backend supports it (NFS, EFS, Azure Files). Third: not setting volumeMode: Filesystem (the default) when you need a raw block device. For databases like Cassandra, raw block mode gives better performance. Fourth: ignoring volumeBindingMode — using Immediate in a multi-zone cluster causes cross-zone attachment failures. Fifth: not monitoring PVC capacity — a full volume causes pod evictions. Set up alerts on kubelet_volume_stats_used_bytes.

pvc-raw-block.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# io.thecodeforge — DevOps tutorial
# PVC for raw block volume (no filesystem)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cassandra-data
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Block  # Raw block device, not filesystem
  resources:
    requests:
      storage: 50Gi
  storageClassName: fast-ssd
Output
persistentvolumeclaim/cassandra-data created
● Production incidentPOST-MORTEMseverity: high

The 4GB Container That Kept Dying

Symptom
A microservice pod kept crashing with OutOfMemoryKilled every 2-3 hours, but memory limits were set to 8GB.
Assumption
Memory leak in the application code.
Root cause
The pod was using a PVC backed by a 4GB AWS EBS gp2 volume. The application wrote logs to a mounted path, and when the volume filled up, the container couldn't write to its log file, causing the OOM killer to terminate it. The 8GB memory limit was irrelevant.
Fix
Increased PVC size to 20GB and added a volumeSizeLimit in the StorageClass to prevent unbounded growth. Also added log rotation in the app.
Key lesson
  • Always monitor disk usage on PVCs — a full volume kills pods faster than a memory leak.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
PVC stuck in Pending with event Failed to provision volume with StorageClass "standard": rpc error: code = InvalidArgument desc = volume type not supported
Fix
1. Run kubectl describe storageclass standard to check provisioner. 2. Verify the CSI driver is installed: kubectl get pods -n kube-system | grep csi. 3. Check cloud provider console for service limits. 4. Fix provisioner name or install missing CSI driver.
Symptom · 02
Pod fails with FailedAttachVolume: Multi-Attach error for volume "pvc-xxx"
Fix
1. Check PVC access mode: kubectl get pvc. 2. Ensure pods using the same PVC are scheduled on the same node (use nodeSelector or pod affinity). 3. If using ReadWriteMany, verify backend supports it (e.g., NFS, EFS). 4. Delete extra pods and reschedule.
Symptom · 03
Volume not deleted after PVC deletion, PV stuck in Released
Fix
1. Check reclaim policy: kubectl get pv pvc-xxx -o yaml | grep reclaimPolicy. 2. If Retain, manually delete PV: kubectl delete pv pvc-xxx. 3. Delete cloud resource via console/CLI. 4. Set reclaimPolicy to Delete in StorageClass.
★ Kubernetes Persistent Volume Claims: Dynamic Provisioning Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
PVC `Pending` with `Failed to provision volume`
Immediate action
Check StorageClass provisioner and CSI driver status
Commands
kubectl describe storageclass <name> | grep Provisioner
kubectl get pods -n kube-system | grep csi
Fix now
Correct provisioner name or reinstall CSI driver
Pod `ContainerCreating` with `Multi-Attach error`+
Immediate action
Identify all pods using the same PVC
Commands
kubectl get pods --all-namespaces -o wide | grep <pvc-name>
kubectl describe pvc <pvc-name>
Fix now
Delete duplicate pods or use pod affinity to co-locate
PV stuck in `Released` after PVC deletion+
Immediate action
Check reclaim policy
Commands
kubectl get pv | grep Released
kubectl get pv <pv-name> -o yaml | grep reclaimPolicy
Fix now
If Retain, delete PV manually: kubectl delete pv <pv-name>
Volume expansion not taking effect+
Immediate action
Check if StorageClass allows expansion
Commands
kubectl get storageclass <name> -o yaml | grep allowVolumeExpansion
kubectl describe pvc <name> | grep Events
Fix now
If false, create new StorageClass with allowVolumeExpansion: true and migrate
Feature / AspectStatic ProvisioningDynamic Provisioning
PV creationManual (kubectl create pv)Automatic via StorageClass
ScalabilityLimited by human opsOn-demand, unlimited
Reclaim policySet per PVSet per StorageClass
Volume expansionManual resize + PV updateAutomatic via PVC patch
Topology awarenessManual zone assignmentAutomatic with WaitForFirstConsumer
ComplexityLowMedium (CSI driver, IAM)
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
storageclass-aws-ebs.yamlapiVersion: storage.k8s.io/v1Why Static Provisioning Is a Production Liability
pvc-mysql.yamlapiVersion: v1Designing a StorageClass That Matches Your Workload
storageclass-retain.yamlapiVersion: storage.k8s.io/v1Reclaim Policies: Delete vs Retain
expand-pvc.shkubectl patch pvc mysql-data -p '{"spec": {"resources": {"requests": {"storage":...Volume Expansion
storageclass-topology.yamlapiVersion: storage.k8s.io/v1Topology Constraints
static-pv-nfs.yamlapiVersion: v1When Dynamic Provisioning Is Overkill
pvc-raw-block.yamlapiVersion: v1Common Mistakes That Burn Production

Key takeaways

1
Dynamic provisioning automates PV creation via StorageClass
always use it in production unless you have a specific reason not to.
2
Set `volumeBindingMode
WaitForFirstConsumer` for stateful workloads in multi-zone clusters to avoid cross-zone attachment failures.
3
Use `reclaimPolicy
Delete for ephemeral data and Retain` only when you have a cleanup process — otherwise you'll burn cloud budget.
4
Monitor PVC disk usage with Prometheus metrics
a full volume kills pods faster than a memory leak.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does `volumeBindingMode: WaitForFirstConsumer` affect PV creation ti...
Q02SENIOR
When would you choose `reclaimPolicy: Retain` over `Delete` in a product...
Q03SENIOR
What happens if you try to shrink a PVC? How do you handle it if you nee...
Q04JUNIOR
What is a StorageClass and how does it enable dynamic provisioning?
Q05SENIOR
A pod using a PVC with `ReadWriteOnce` is scheduled on node A. Another p...
Q06SENIOR
How would you design StorageClasses for a multi-zone Kubernetes cluster ...
Q01 of 06SENIOR

How does `volumeBindingMode: WaitForFirstConsumer` affect PV creation timing and what problem does it solve?

ANSWER
It delays PV creation until a pod using the PVC is scheduled. This solves the cross-zone attachment problem: without it, the PV might be created in a zone different from the pod, causing attachment failures. It also avoids orphaned PVs if the pod never gets scheduled.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
How do I set a default StorageClass in Kubernetes?
02
What's the difference between `Delete` and `Retain` reclaim policies?
03
How do I expand a PVC without downtime?
04
Why is my PVC stuck in `Pending` even though the StorageClass exists?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
Verified
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
🔥

That's Kubernetes. Mark it forged?

3 min read · try the examples if you haven't

Previous
Kubernetes Persistent Volumes and Storage Classes
34 / 43 · Kubernetes
Next
Kubernetes Pod Disruption Budgets: High Availability