
September 10, 2026
14 min read
By Kokil Thapa | Last reviewed: September 2026
Stateful workloads need disk that survives pod restarts. StorageClasses and Dynamic Provisioning solve that in Kubernetes by letting a PersistentVolumeClaim request storage without you pre-creating every volume by hand. If you run Laravel queues, MySQL, Redis, or document uploads on a cluster, this is the layer that decides whether a pod gets fast SSD, cheap bulk storage, or nothing at all. The concepts look simple on paper. In practice, a wrong storageClassName or reclaim policy has deleted production data on real teams. This guide walks through how the pieces connect, how to write a StorageClass manifest, and what to check before you deploy.
storageClassName in the claim.Before diving into YAML, it helps to see how Linux system administration for production servers and cluster storage overlap. On a single VPS you pick a mount point once. In Kubernetes, every stateful service repeats that decision through claims and classes. The Vault dynamic secrets pattern for databases solves credential rotation; StorageClasses solve the parallel problem for disk allocation.
What are StorageClasses in Kubernetes?
A StorageClass is a cluster-scoped object that describes how storage should be provisioned. It is not the volume itself. Think of it as a template: provisioner type, performance tier, filesystem, and what happens when the claim is deleted.
Each StorageClass has a provisioner field. That string tells Kubernetes which plugin creates volumes. Examples include kubernetes.io/aws-ebs on older clusters and ebs.csi.aws.com on modern CSI-based setups. One cluster often runs several classes side by side: fast-ssd for databases, standard for logs, backup for snapshots.
Static vs dynamic provisioning
Static provisioning means an admin creates PersistentVolumes ahead of time. A PVC binds to a matching free PV. That works on small clusters but does not scale. Every new database means manual disk creation and label matching.
Dynamic provisioning inverts the flow. The PVC carries a storageClassName. The provisioner watches unbound claims, reads the class, calls the storage backend, and creates a PV that binds immediately. No pre-staged volumes. No capacity guessing weeks in advance.
Default StorageClass behaviour
Clusters mark one StorageClass as default with the annotation storageclass.kubernetes.io/is-default-class: "true". PVCs that omit storageClassName inherit that class. Only one default should exist; duplicates produce confusing behaviour across namespaces.
On managed clusters (EKS, GKE, AKS), a default class often ships pre-installed. Self-managed clusters on Ubuntu servers need explicit setup. I've seen Laravel file-upload pods stuck in Pending because no default class existed and the Helm chart never set one.
How does dynamic provisioning work in Kubernetes?
Dynamic provisioning follows a strict sequence. Understanding each step helps you debug PVCs that never bind.
- A developer or CI pipeline applies a PersistentVolumeClaim with
storageClassName, access mode, and requested size. - The PVC enters
Pendinguntil a matching PV appears. - The external-provisioner sidecar (or in-tree plugin on legacy clusters) detects the claim.
- It reads the referenced StorageClass: provisioner name, parameters, reclaim policy, volume binding mode.
- The provisioner calls the cloud or local API and creates a disk or directory.
- Kubernetes creates a PersistentVolume object representing that disk.
- The PVC binds to the PV. The pod scheduler can now attach the volume to a node.
Volume binding modes
StorageClasses support two binding modes. Immediate provisions and binds as soon as the PVC is created. That is fine for ReadWriteMany file shares or cloud disks that attach anywhere.
WaitForFirstConsumer delays provisioning until a pod is scheduled. The provisioner creates the volume in the same zone as the node. Use this for zone-local disks on AWS EBS or GCP Persistent Disk. Without it, a pod in ap-south-1b may get a volume in ap-south-1a and fail to attach.
For Nepal-hosted workloads on ap-south-1 (Mumbai), zone alignment matters. Latency to the disk is low, but cross-zone attachment is impossible on many block stores.
Access modes and what they allow
- ReadWriteOnce (RWO): one node read-write. Standard for MySQL, PostgreSQL, Redis persistence.
- ReadOnlyMany (ROX): many nodes read-only. Config bundles, static assets.
- ReadWriteMany (RWX): many nodes read-write. Requires NFS, CephFS, EFS, or similar. Needed when multiple Laravel queue workers share upload storage.
The StorageClass does not grant access modes by itself. The underlying provisioner must support them. A class backed by AWS EBS supports RWO only. Claiming RWX against it fails at bind time.
How do you configure a StorageClass for dynamic provisioning?
Start with a minimal StorageClass, then tune parameters for your cloud or on-prem backend. The official Kubernetes StorageClass documentation lists every field; the examples below reflect patterns used in production clusters in 2026.
Example: AWS EBS via CSI
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-fast
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true Key fields explained:
provisioner— must match the installed CSI driver name exactly.parameters— driver-specific; wrong keys are silently ignored or cause provision failures.reclaimPolicy—Deleteremoves the cloud disk when the PVC is deleted;Retainkeeps it for manual recovery.allowVolumeExpansion— lets you grow the PVC without remounting a new volume (driver must support it).
Example: PVC that triggers dynamic provisioning
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: gp3-fast
resources:
requests:
storage: 50Gi Apply the PVC. Run kubectl get pvc mysql-data -n production. Status should move from Pending to Bound within seconds on healthy clusters. Run kubectl describe pvc mysql-data -n production if it stalls; events show provisioner errors clearly.
Local or self-hosted options
Not every team runs managed Kubernetes. On bare-metal or VPS clusters, common choices include:
- local-path-provisioner — quick dev setups; data lives on a specific node disk.
- Longhorn — distributed block storage with replication across nodes.
- NFS subdir external provisioner — RWX over an NFS server you maintain.
These map to the same StorageClass model. The provisioner string changes. The dynamic flow does not. Teams migrating from traditional VPS hosting with manual disk mounts often underestimate how node-local storage breaks when pods reschedule.
Helm and GitOps integration
Charts for MySQL, PostgreSQL, or Redis accept persistence.storageClass values. Pin the class name in your values file. Do not rely on the cluster default in production. Defaults change when platform teams upgrade CSI drivers.
Store StorageClass manifests in Git alongside application manifests. The same GitLab CI pipeline that deploys Laravel apps can apply infrastructure objects with kubectl apply -f storage/. Treat storage config like any other infrastructure-as-code resource — versioned, reviewed, and repeatable.
Which StorageClass provisioner should you choose?
Provisioner choice depends on access mode, performance, cost, and whether you need snapshots. The table below compares common options for production web and database workloads.
| Backend | Access modes | Best for | Watch out for |
|---|---|---|---|
| Cloud block (EBS, PD, Azure Disk) | RWO | MySQL, PostgreSQL, single-node Redis | Zone lock-in; use WaitForFirstConsumer |
| Cloud file (EFS, Filestore, Azure Files) | RWX | Shared uploads, media libraries | Higher cost; latency vs local disk |
| Longhorn / Ceph RBD | RWO, some RWX | Self-managed HA clusters | Ops overhead; monitor replication health |
| NFS provisioner | RWX | Legacy shared storage, dev clusters | Single NFS server = single point of failure |
| local-path | RWO | Dev, CI, single-node test | Data lost or orphaned when node dies |
For a typical enterprise Laravel application with MySQL and Redis, two classes cover most cases: fast RWO for the database and RWX for user-uploaded documents. Legal-tech portals with client document sharing especially need RWX or object storage (S3/MinIO) instead of pretending block storage can mount on multiple pods.
On a booking platform like Adventure Third Pole Trek, media and itinerary PDFs belong in object storage or RWX file storage. Database rows stay on RWO block volumes with nightly snapshots.
Reclaim policies in production
Delete suits ephemeral dev namespaces. One kubectl delete namespace staging wipes disks too. That is convenient until someone typos the namespace name.
Retain suits production databases. Deleting the PVC leaves the PV in Released state and the cloud disk intact. You recover data manually. The trade-off is orphaned disks and monthly charges if nobody cleans them up.
Set reclaim policy on the StorageClass, not the PVC. New volumes inherit it. Changing the class later does not retroactively alter existing PVs.
Volume expansion and snapshots
Enable allowVolumeExpansion: true on classes backing databases that grow over time. After expanding the PVC, the filesystem may still need resize2fs or xfs_growfs inside the pod. Some CSI drivers handle that automatically; verify for your driver.
VolumeSnapshotClass is the snapshot counterpart to StorageClass. It defines which CSI driver creates point-in-time copies. Pair snapshot schedules with Retain reclaim for databases you cannot afford to lose. The Kubernetes CSI project documentation lists snapshot-capable drivers.
What are common mistakes with StorageClasses and Dynamic Provisioning?
Most storage incidents are configuration errors, not provisioner bugs. These patterns show up repeatedly across teams.
PVC stuck in Pending
Check four things first:
storageClassNamematches an existing StorageClass (case-sensitive).- The provisioner pod is running:
kubectl get pods -n kube-system | grep csi. - Access mode is supported by the backend.
- Cloud IAM or service account permissions allow disk creation.
kubectl describe pvc events usually state the exact failure. "no persistent volumes available" on a dynamic class often means the provisioner is down or misnamed.
Deleting namespaces with Delete reclaim
A namespace delete cascades to PVCs. With reclaimPolicy: Delete, the provisioner removes cloud disks. Production MySQL gone in one command. Use Retain on production classes. Add finalizers or backup jobs if your platform allows namespace-level deletes.
Mixing storage across environments
Staging and production should use differently named StorageClasses. gp3-staging with Delete and gp3-production with Retain prevents a values-file copy error from pointing staging config at production disks.
Ignoring admission control
Validating admission webhooks can enforce allowed StorageClass names per namespace. Only fast-ssd and standard in production; block local-path outside dev. See admission controllers and validating webhooks for the pattern. Storage policy belongs in the same guardrails as pod security standards.
Monitoring and cost
Orphaned Retain volumes accumulate silently. Tag cloud disks at provision time via StorageClass parameters where the driver supports it. Review unattached volumes monthly. Tools like JSON formatters help parse CSI driver logs exported from Loki or CloudWatch during incident review.
Pair storage monitoring with broader AIOps-style infrastructure observability. Alert on PVC Pending longer than five minutes. Alert on disk usage above eighty percent on database volumes.
How do StorageClasses fit into a full application stack?
StorageClasses sit below your application but above raw cloud APIs. A Laravel 13 app on PHP 8.3 with MySQL 9.7 does not know about StorageClasses. Kubernetes injects a mounted path. Your job is to wire the right class into Helm values or Kustomize patches.
For teams running hybrid setups — Laravel on VPS plus workers on Kubernetes — keep authoritative data in one place. Do not split MySQL between a managed RDS instance and an in-cluster PVC without a documented failover plan. Ongoing support and maintenance contracts should list which StorageClasses production uses and who owns backup verification.
WordPress on Kubernetes is uncommon for small Nepali business sites. WooCommerce 11.1 on a single VPS with proper backups is often cheaper. StorageClasses earn their complexity when you need horizontal pod scaling, zero-downtime deploys, or multi-AZ database failover. Match the tool to the scale.
Document uploads on legal portals benefit from object storage (S3-compatible) rather than RWX PVCs when possible. Object storage scales cheaper and avoids file-lock issues. Use StorageClasses for database persistence and Redis AOF files; use buckets for PDFs and scanned documents. On projects like Mijar Law Associates, separating blob storage from relational data simplified backup and compliance discussions.
When provisioning clusters with Ansible playbooks for server setup or Terraform, install the CSI driver before applying StorageClass manifests. Order matters: driver DaemonSet healthy, then StorageClass, then PVC, then StatefulSet.
Key Takeaways
- StorageClasses define provisioner, parameters, reclaim policy, and binding mode — they are templates, not volumes.
- Dynamic provisioning creates PVs on demand when a PVC references a StorageClass; no manual PV staging required.
- Use
WaitForFirstConsumerfor zone-local block storage; use RWX-capable provisioners only when multiple pods must write the same path. - Set
reclaimPolicy: Retainon production database classes to prevent accidental disk deletion with the PVC. - Pin
storageClassNameexplicitly in Helm values and GitOps repos — never rely silently on cluster defaults. - Debug Pending PVCs with
kubectl describe pvc; check provisioner health, access modes, and cloud permissions first.
People Also Ask
What is the difference between a PersistentVolume and a StorageClass?
A PersistentVolume is a concrete piece of storage represented as a Kubernetes object — a 50 Gi EBS volume, for example. A StorageClass is the recipe for creating PVs. Dynamic provisioning uses the StorageClass to manufacture PVs when PVCs appear. You rarely create PVs by hand once dynamic provisioning is configured.
Can I change the StorageClass on an existing PVC?
No. storageClassName is immutable after creation. To move data, snapshot the volume (if supported), create a new PVC with the target class, restore into it, and swap the workload reference. Plan storage tiers before go-live rather than migrating under load.
What happens if no StorageClass matches my PVC?
The PVC stays Pending indefinitely unless a statically created PV happens to match size and access mode. If the cluster has a default StorageClass and your PVC omits the field, the default applies. If there is no default and no matching class name, nothing provisions.
Are in-tree volume plugins still used in 2026?
New clusters should use CSI drivers exclusively. In-tree plugins for AWS, GCE, and Azure are deprecated and removed in recent Kubernetes versions. The Kubernetes CSI volume documentation describes the replacement model. Upgrade paths require migrating StorageClass provisioner fields to CSI driver names.
Deploy storage with the same rigour as application code
StorageClasses and Dynamic Provisioning remove manual disk wrangling from day-to-day Kubernetes operations. They also concentrate risk: one wrong reclaim policy or missing class name stops a database or destroys it. Define your classes in Git, use Retain for production data, match access modes to real workload needs, and validate PVC binding in CI before merge.
If you are moving a stateful Laravel, eCommerce, or legal-tech platform onto Kubernetes and want storage designed alongside the application, contact us for a architecture review. You can also browse custom software development services or explore the full project portfolio for examples of production systems shipped with proper persistence and backup planning.
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.

