Kubernetes volumes are the storage layer for pods. The three abstractions: PersistentVolume (PV, the actual storage in the cluster), PersistentVolumeClaim (PVC, a request for storage by a pod), and StorageClass (the provisioner template that creates PVs on demand). The right pattern for most apps: a PVC backed by a StorageClass that auto-provisions. The team that runs production sets up the StorageClass once, then forgets about it; the PVC is the only object the deployment manifest references.
Table of contents
- The three abstractions
- The right pattern: StorageClass + PVC + pod
- StorageClass with parameters
- Access modes: when to use each
- Reclaim policy: what happens to the volume when the PVC is deleted
- How this fits the rest of the stack
- FAQ
The three abstractions
PersistentVolume (PV) - a piece of storage in the cluster, provisioned by an admin or dynamically by a StorageClass. Has a capacity, an access mode, a reclaim policy, and a storage class.
PersistentVolumeClaim (PVC) - a request for storage by a user (or a pod). Names a size, an access mode, and optionally a storage class. When a PVC is created, Kubernetes binds it to a matching PV (static) or triggers dynamic provisioning (via StorageClass).
StorageClass - a template for dynamic provisioning. Names a provisioner (aws-ebs, gce-pd, nfs-csi, etc.) and parameters. When a PVC references a StorageClass, the provisioner creates a new PV automatically.
The team that has a managed Kubernetes (EKS, GKE, AKS, RunxBuild) gets a default StorageClass out of the box. The team that runs k8s on bare metal installs a CSI driver and creates the StorageClass.
The right pattern: StorageClass + PVC + pod
Most apps just need a PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myapp-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
The accessModes are ReadWriteOnce (RWO, one node can mount read-write), ReadOnlyMany (ROX, many nodes can mount read-only), ReadWriteMany (RWX, many nodes can mount read-write - requires NFS or a distributed filesystem).
Mount in a pod:
volumes:
- name: data
persistentVolumeClaim:
claimName: myapp-data
containers:
- name: myapp
volumeMounts:
- name: data
mountPath: /var/lib/myapp
That’s the whole pattern. The team that deploys a stateful app uses this every time.
StorageClass with parameters
A StorageClass controls how PVs are provisioned. Example for AWS EBS:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
The team that has multiple tiers (fast SSD, slow HDD, replicated) sets up a StorageClass per tier and references the right one from each PVC.
Access modes: when to use each
ReadWriteOnce (RWO) - the default. One node mounts the volume read-write. Most databases (Postgres, MySQL, Mongo) use RWO. The team that picks RWO for a single-pod database is correct.
ReadOnlyMany (ROX) - many nodes mount read-only. Static assets, configuration data, read-only sidecars.
ReadWriteMany (RWX) - many nodes mount read-write. Requires a distributed filesystem (NFS, CephFS, GlusterFS, EFS). The team that runs a clustered database (Cassandra, Elasticsearch) uses RWX; the team that runs a single-instance Postgres does not need it.
Reclaim policy: what happens to the volume when the PVC is deleted
Retain - the PV is kept, the data is preserved. The team that has production data uses Retain. The admin has to manually clean up.
Delete - the PV and the underlying storage are deleted when the PVC is deleted. The team that runs dev/staging uses Delete for fast cleanup.
Recycle (deprecated) - basic scrub and re-provision. Most provisioners do not support it anymore.
The team that uses dynamic provisioning with Delete loses data when the PVC is deleted. The right pick: Retain for anything you care about, Delete for ephemeral data.
FAQ
Why is my PVC stuck in Pending?
Either no PV matches (size too big, access mode not supported, wrong storage class) or the dynamic provisioner cannot create a volume (quota exceeded, no backing storage, IAM permission). The team that debugs Pending runs kubectl describe pvc <name> to read the events.
Can I resize a PVC?
Yes, if the storage class allows it (most do). Edit the PVC: kubectl edit pvc <name>, change spec.resources.requests.storage. The volume is resized (if the underlying provider supports online expansion).
What is the difference between a Volume and a PVC?
A Volume is the in-pod mount, declared in the pod spec. A PVC is the cluster-level request that backs the volume. The pod references the PVC, the PVC references a PV (directly or via StorageClass).
Should I use a single PVC for everything?
No. One PVC per data set is the right pattern. The team that has myapp-data (the database) and myapp-uploads (user files) as separate PVCs can back up, restore, and resize them independently.
Can multiple pods mount the same PVC?
Depends on the access mode. RWO: no. ROX: yes. RWX: yes (requires NFS or similar). The team that runs a clustered database with multiple replicas uses RWX; the team that runs a single-instance Postgres uses RWO.
How this fits the rest of the stack
For a sense of what the full project costs before it commits, the RunxBuild hosting calculator shows the line items together. The API, the database, the storage, the worker, the bandwidth - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers.
Useful related references: