K8s PersistentVolumeClaimSpec Reference
Complete PersistentVolumeClaimSpec reference for Kubernetes. accessModes, storageClassName, resources, selector, volumeMode, and dataSource explained.
π‘ Quick Answer:
PersistentVolumeClaimSpecdefines storage requirements:accessModes(ReadWriteOnce/ReadWriteMany/ReadOnlyMany),resources.requests.storage(size like 10Gi),storageClassName(which provisioner to use), and optionallyvolumeMode(Filesystem or Block),selector(bind to specific PV), anddataSource(clone or snapshot). Most PVCs only need accessModes + storage size + storageClassName.
The Problem
PVC specs have many fields and itβs unclear which are required, what values are valid, and how they interact with StorageClasses and PVs.
The Solution
Minimal PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standardComplete PersistentVolumeClaimSpec
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data
namespace: production
spec:
# Required: How the volume can be mounted
accessModes:
- ReadWriteOnce # Single node read-write
# - ReadWriteMany # Multiple nodes read-write (NFS, CephFS)
# - ReadOnlyMany # Multiple nodes read-only
# - ReadWriteOncePod # Single pod read-write (K8s 1.27+)
# Required: Storage size
resources:
requests:
storage: 10Gi # Minimum size
# limits: # Optional β rarely used
# storage: 20Gi
# Which StorageClass to use (omit for cluster default)
storageClassName: gp3-encrypted
# storageClassName: "" # Empty string = bind to pre-provisioned PV only
# Filesystem (default) or Block
volumeMode: Filesystem
# volumeMode: Block # Raw block device β no filesystem
# Bind to specific PV (static provisioning)
# selector:
# matchLabels:
# app: database
# matchExpressions:
# - key: environment
# operator: In
# values: ["production"]
# Clone from existing PVC or restore from snapshot
# dataSource:
# name: existing-pvc
# kind: PersistentVolumeClaim
# dataSource:
# name: my-snapshot
# kind: VolumeSnapshot
# apiGroup: snapshot.storage.k8s.io
# Cross-namespace clone (K8s 1.29+)
# dataSourceRef:
# name: source-pvc
# kind: PersistentVolumeClaim
# namespace: other-namespaceAccess Modes Comparison
| Mode | Short | Nodes | Use Case |
|---|---|---|---|
| ReadWriteOnce | RWO | 1 node | Databases, single-writer apps |
| ReadWriteMany | RWX | Multiple | Shared data, CMS uploads, ML datasets |
| ReadOnlyMany | ROX | Multiple | Config files, static assets |
| ReadWriteOncePod | RWOP | 1 pod | Strict single-writer (K8s 1.27+) |
StorageClass Examples
# List available StorageClasses
kubectl get storageclass
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE
# gp3 (default) ebs.csi.aws.com Delete WaitForFirstConsumer
# efs-sc efs.csi.aws.com Retain Immediate
# standard kubernetes.io/gce-pd Delete Immediate
# See default StorageClass
kubectl get sc -o jsonpath='{.items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")].metadata.name}'Use in Pods
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: myapp:v1
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: my-data # References the PVC
readOnly: false
---
# StatefulSet with volumeClaimTemplates (auto-creates PVCs)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database
spec:
serviceName: database
replicas: 3
template:
spec:
containers:
- name: db
volumeMounts:
- name: data
mountPath: /var/lib/db
volumeClaimTemplates:
- metadata:
name: data
spec: # This IS the PersistentVolumeClaimSpec
accessModes: ["ReadWriteOnce"]
storageClassName: gp3-encrypted
resources:
requests:
storage: 50GiVolume Expansion
# Check if StorageClass allows expansion
kubectl get sc gp3 -o jsonpath='{.allowVolumeExpansion}'
# true
# Expand PVC (only increase β shrinking not supported)
kubectl patch pvc my-data -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
# Check expansion status
kubectl get pvc my-data -o yaml | grep -A5 conditionsCommon Issues
PVC stuck in Pending
Check events: kubectl describe pvc my-data. Common causes: no matching StorageClass, no capacity, WaitForFirstConsumer binding mode (needs a pod to schedule first).
βstorageClassName must be providedβ error
No default StorageClass set. Either specify one explicitly or mark a StorageClass as default.
accessMode mismatch
PV and PVC accessModes must be compatible. EBS only supports RWO β if you need RWX, use EFS, NFS, or CephFS.
Best Practices
- Always specify
storageClassNameβ donβt rely on default (it can change) - Use
ReadWriteOncePodfor databases β prevents accidental multi-attach - Set
WaitForFirstConsumeron StorageClass β ensures volume is in the same zone as the pod - Use
volumeClaimTemplatesin StatefulSets β automatic per-replica PVCs - Enable volume expansion on StorageClass β avoid recreating PVCs for resizing
Key Takeaways
- Most PVCs need only 3 fields: accessModes, storage size, and storageClassName
- RWO for databases, RWX for shared storage, RWOP for strict single-writer
storageClassName: ""binds to pre-provisioned PVs only (no dynamic provisioning)dataSourceenables PVC cloning and snapshot restore- StatefulSet
volumeClaimTemplatesspec IS a PersistentVolumeClaimSpec

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
