
September 09, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Stateful workloads break the moment you treat pods like cattle and disks like scratch space. Kubernetes Persistent Volumes and Storage exist so databases, upload directories, and queue data survive rescheduling, node drains, and rolling updates. If you run Laravel queues, document portals, or eCommerce carts on Kubernetes, you need a clear model for how data outlives any single container. This guide walks through PVs, PVCs, StorageClasses, CSI drivers, access modes, and the production mistakes I see on real clusters.
What are Kubernetes Persistent Volumes and Storage in plain terms?
Pods are ephemeral. Their root filesystem disappears when the pod is deleted. Docker volumes solve this on a single host, but Kubernetes schedules pods across many nodes. You need cluster-level storage objects that survive pod lifecycle events.
Three objects form the core contract:
- PersistentVolume (PV) — a piece of storage in the cluster, like a 50 GiB block volume or NFS export.
- PersistentVolumeClaim (PVC) — a pod's request for storage with size, access mode, and optional StorageClass.
- StorageClass — a template that tells a provisioner how to create PVs dynamically.
Think of PV as the inventory shelf, PVC as the order form, and StorageClass as the factory line that builds new shelves on demand. The Kubernetes control plane binds a PVC to a matching PV, then kubelet mounts it into the pod spec.
How do you create a PersistentVolumeClaim and mount it in a pod?
Most teams use dynamic provisioning. You define a StorageClass once, then every PVC triggers automatic PV creation. Static provisioning still matters when you attach pre-existing NFS exports or legacy SAN LUNs.
Step 1: Define a StorageClass
Cloud clusters usually ship with a default class. For AWS EBS, GCE PD, or DigitalOcean block storage, confirm the provisioner name matches your CSI driver. A minimal StorageClass for a cloud disk might look like this:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
fsType: ext4
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true WaitForFirstConsumer delays binding until Kubernetes knows which node will run the pod. That prevents provisioning a volume in the wrong availability zone. For zone-aware clusters, this setting saves hours of debugging.
Step 2: Create the PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi The PVC stays in Pending until a matching PV exists or the provisioner creates one. Check status with kubectl get pvc app-data -n production. The VOLUME column shows the bound PV name once ready.
Step 3: Mount the PVC in a Deployment or StatefulSet
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-api
spec:
replicas: 1
template:
spec:
containers:
- name: php-fpm
image: myregistry/laravel-api:2026.09
volumeMounts:
- name: storage
mountPath: /var/www/storage/app
volumes:
- name: storage
persistentVolumeClaim:
claimName: app-data On a production Laravel application, mount storage/app for uploads and keep the database on a separate PVC. Never share one ReadWriteOnce volume across multiple replicas unless your app handles file locking. That pattern breaks under load.
Which access modes and reclaim policies should you choose?
Access modes define how many nodes can mount a volume simultaneously. Reclaim policies define what happens to the PV after the PVC is deleted. Getting either wrong causes data loss or stuck volumes.
| Access mode | Meaning | Typical use |
|---|---|---|
ReadWriteOnce (RWO) | Single node read/write | MySQL, PostgreSQL, single-replica app storage |
ReadOnlyMany (ROX) | Many nodes read-only | Static assets, config bundles on shared NFS |
ReadWriteMany (RWX) | Many nodes read/write | Shared uploads, NFS-backed Laravel storage |
ReadWriteOncePod (RWOP) | Single pod read/write | Strict pod-level isolation on supported CSI drivers |
Reclaim policies matter for cost and safety:
- Delete — the PV and underlying cloud disk are removed when the PVC is deleted. Fine for dev. Dangerous for production databases without backups.
- Retain — the PV object stays, and the admin must manually reclaim the disk. Safer for production when paired with snapshots.
I prefer Retain on production database StorageClasses. Pair it with volume snapshots and Velero cluster backups. A mistaken kubectl delete pvc should not wipe five years of orders.
What storage backends work best for Kubernetes Persistent Volumes and Storage?
Your backend choice depends on access mode needs, performance, and operational budget. Cloud block storage is the default path. Shared file systems enter when multiple pods need the same directory.
| Backend | Access | Pros | Cons |
|---|---|---|---|
| Cloud block (EBS, PD, DO Volume) | RWO | Fast, managed, simple CSI | Zone-locked, not shared across nodes |
| NFS | RWX | Shared files, familiar ops | Latency, single-server bottleneck |
| Ceph RBD / CephFS | RWO / RWX | Scale-out, snapshots | Heavy ops, needs tuning |
| OpenEBS | RWO | K8s-native on bare metal | Replica overhead on small clusters |
| GlusterFS | RWX | Distributed file layer | Declining community momentum |
Local SSD (local-path) | RWO | Low latency | Not portable across nodes |
For a booking platform like Adventure Third Pole Trek, block storage on a single-replica StatefulSet handles MySQL well. Shared RWX storage fits document upload directories when you scale PHP-FPM horizontally. Match the backend to the workload, not the other way around.
Modern clusters use the Container Storage Interface (CSI) instead of in-tree plugins. Install the vendor CSI driver, deploy its controller and node DaemonSets, then reference the driver in StorageClass provisioner fields. The official Persistent Volumes documentation remains the authoritative reference for object fields and status phases.
How do StatefulSets differ from Deployments for persistent storage?
Deployments treat pods as interchangeable. StatefulSets give each pod a stable network identity and a dedicated PVC through volumeClaimTemplates. That pattern suits MySQL replicas, Redis, Elasticsearch, and Kafka.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:9.7
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi StatefulSet pod names are predictable: mysql-0, mysql-1, mysql-2. Each gets its own PVC named data-mysql-0 and so on. Deleting the StatefulSet does not delete those PVCs by default. That protects data but can leave orphaned disks billing your cloud account.
For Laravel on Kubernetes, keep the web tier on Deployments with object storage (S3-compatible) for uploads when possible. Reach for StatefulSets when you self-host MySQL or Redis inside the cluster. Managed databases outside the cluster simplify ops for small teams.
What production mistakes break Kubernetes Persistent Volumes and Storage?
Storage failures rarely announce themselves during deployment. They surface at 2 a.m. when a pod reschedules to another node and never mounts.
Common failure patterns
- Multi-replica Deployment + RWO volume — only one pod mounts; others stay Pending. Use RWX or external object storage.
- Wrong availability zone — volume provisioned in zone A, pod scheduled in zone B. Use
WaitForFirstConsumer. - Missing CSI node plugin — controller creates disk but kubelet cannot attach. Check DaemonSet on the target node.
- Delete reclaim on production — PVC deletion wipes the cloud disk. Switch to Retain and automate snapshot cleanup.
- No backup path — PV redundancy is not backup. Schedule VolumeSnapshot objects or Velero jobs.
- Running out of inode or disk space — monitor used bytes and inodes. Expand PVCs if
allowVolumeExpansion: trueis set.
When a pod enters ContainerCreating forever, describe the pod and check events. Messages like FailedAttachVolume or FailedMount point to attach or filesystem issues. The CrashLoopBackOff debugging guide covers related scheduling problems.
Expanding a PVC without downtime
When StorageClass allows expansion, patch the PVC request and restart the pod if the filesystem does not auto-resize:
kubectl patch pvc app-data -n production \
-p '{"spec":{"resources":{"requests":{"storage":"40Gi"}}}}'
kubectl exec -it deploy/laravel-api -- df -h /var/www/storage/app Not every CSI driver supports online expansion. Test in staging first. Document the steps in your runbook alongside PV lifecycle management.
Security and performance notes
Encrypt cloud disks at rest through StorageClass parameters. Use fsGroup in pod security context so non-root containers can write to mounted volumes. For sensitive legal-tech document stores, combine encrypted PVs with Secrets management for credentials.
Performance tuning starts with disk type and IOPS limits. A Rs 3,000/month (~USD 22) standard disk will bottleneck a busy MySQL instance. Match disk tier to query load. Review cluster performance tuning alongside storage metrics.
I've seen teams compare SAN versus NAS architectures before choosing in-cluster backends. Block SAN behaviour maps cleanly to RWO cloud volumes. NAS file shares map to NFS or CephFS when you need RWX.
Key Takeaways
- Bind durable data through PV + PVC; let StorageClass and CSI handle dynamic provisioning.
- Use RWO block storage for databases; use RWX file storage only when multiple pods share files.
- Set
reclaimPolicy: Retainand snapshot production volumes before any PVC deletion. - Prefer
WaitForFirstConsumeron zone-aware clusters to avoid attach failures. - StatefulSets with
volumeClaimTemplatesgive each replica its own PVC; Deployments do not. - PV redundancy is not backup — schedule Velero or VolumeSnapshot jobs and test restores.
People Also Ask
What is the difference between a PersistentVolume and a PersistentVolumeClaim?
A PersistentVolume is cluster storage inventory — the actual disk or export. A PersistentVolumeClaim is an application's request for storage with size and access requirements. Kubernetes binds one PVC to one PV when capacity, mode, and StorageClass match.
Can multiple pods share the same PersistentVolumeClaim?
Only when the volume supports ReadWriteMany or ReadWriteOncePod and the storage backend allows concurrent mounts. Standard cloud block volumes use ReadWriteOnce, so a single node mounts them. For shared Laravel upload folders, use NFS, CephFS, or object storage instead.
What happens to data when you delete a PersistentVolumeClaim?
It depends on the StorageClass reclaim policy. With Delete, the underlying cloud disk is destroyed. With Retain, the PV remains in Released state and an admin must manually recover or rebind the disk. Production clusters should use Retain plus automated snapshots.
Do you need StatefulSets for persistent storage?
Not always. A Deployment with a single replica and one PVC works for simple stateful apps. StatefulSets add stable pod names, ordered rollout, and per-pod PVC templates — essential for clustered databases and distributed systems that rely on identity.
Build stateful workloads that survive real operations
Kubernetes Persistent Volumes and Storage are not optional once your application holds user uploads, orders, or case files. Define StorageClasses with sane reclaim policies, match access modes to replica counts, install CSI drivers correctly, and backup outside the cluster lifecycle. If you are moving a Laravel app, legal portal, or eCommerce platform onto Kubernetes and want storage designed for production from day one, enterprise application development and Linux system administration cover architecture through ongoing ops. Need help validating YAML before apply? Use the JSON formatter to inspect rendered manifests, then contact us to review your cluster storage design.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

