K8s Storage Basics
This section covers the core Kubernetes storage objects - StorageClasses, PersistentVolumeClaims, and PersistentVolumes - through small, runnable manifests you can apply to any cluster.
Search across all documentation pages
This section covers the core Kubernetes storage objects - StorageClasses, PersistentVolumeClaims, and PersistentVolumes - through small, runnable manifests you can apply to any cluster.
kubectl matching your control plane minor version.kubectl get storageclassEvery dynamic PVC without an explicit class uses the one marked default.
kubectl get storageclass -o wide(default) backs any PVC that omits storageClassName.PROVISIONER shows which CSI driver fulfills claims, for example ebs.csi.aws.com.RECLAIMPOLICY of Delete means the disk is destroyed with the PVC.A PVC is the request an application makes for storage.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10GiaccessModes of ReadWriteOnce means a single node mounts it read-write.requests.storage is the minimum capacity; the driver may round up.storageClassName uses the default class.Pending until a Pod consumes it, if the class uses WaitForFirstConsumer.Binding is where the request meets a real disk.
kubectl apply -f pvc.yaml
kubectl get pvc data -wPending to Bound once a PV is provisioned and matched.VOLUME column shows the auto-generated PV name.-w streams status changes so you see the transition live.Pending almost always means no provisioner or no schedulable consumer.Storage is only provisioned and mounted when a Pod references the claim.
apiVersion: v1
kind: Pod
metadata:
name: writer
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo hi > /data/file && sleep 3600"]
volumeMounts:
- name: vol
mountPath: /data
volumes:
- name: vol
persistentVolumeClaim:
claimName: datavolumes names the PVC; volumeMounts places it at a path in the container./data survives Pod restarts because it lives on the PV.mountPath must be an absolute path inside the container.Persistence is the entire point, so prove it.
kubectl delete pod writer
kubectl apply -f writer.yaml
kubectl exec writer -- cat /data/filecat shows the earlier content, confirming durability.The PV is the cluster-side record of the real disk.
kubectl get pv
kubectl describe pv <name>describe shows capacity, access modes, reclaim policy, and the backing volume handle.Claim links back to the PVC that owns it.StorageClass records which class provisioned it.Source section names the CSI driver and vendor volume ID.Explicitly selecting a class avoids relying on the default.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fast-data
spec:
storageClassName: fast-ssd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20GistorageClassName pins the tier so the claim is never at the mercy of the cluster default.Pending forever.A StorageClass encodes provisioning parameters for a disk tier.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "6000"
throughput: "250"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Deleteparameters are driver-specific; here they set an AWS gp3 disk with fixed IOPS and throughput.WaitForFirstConsumer delays provisioning so the disk lands in the Pod's zone.allowVolumeExpansion: true permits later growth of bound PVCs.reclaimPolicy: Delete cleans up the disk with the claim.Most CSI drivers grow volumes without downtime.
kubectl patch pvc data -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
kubectl get pvc dataallowVolumeExpansion: true on the class.CAPACITY column to confirm the new size.StatefulSets template a PVC per Pod for clustered data.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cache
spec:
serviceName: cache
replicas: 3
selector:
matchLabels: { app: cache }
template:
metadata:
labels: { app: cache }
spec:
containers:
- name: redis
image: redis:7
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gidata-cache-0, data-cache-1, and so on.Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026