Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

StorageClasses and Dynamic Provisioning

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.

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.

StorageClasses and Dynamic ProvisioningPoduses volumeMountPVCstorage requestStorageClassprovisioner rulesPVbound volumeCSI Drivercloud or local APICloud DiskEBS, PD, Azure DiskProvisioner reads StorageClass and creates backing storage
StorageClasses and Dynamic Provisioning connect PVCs to provisioners that create PersistentVolumes automatically

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.

  1. A developer or CI pipeline applies a PersistentVolumeClaim with storageClassName, access mode, and requested size.
  2. The PVC enters Pending until a matching PV appears.
  3. The external-provisioner sidecar (or in-tree plugin on legacy clusters) detects the claim.
  4. It reads the referenced StorageClass: provisioner name, parameters, reclaim policy, volume binding mode.
  5. The provisioner calls the cloud or local API and creates a disk or directory.
  6. Kubernetes creates a PersistentVolume object representing that disk.
  7. The PVC binds to the PV. The pod scheduler can now attach the volume to a node.
Dynamic Provisioning Sequence1. Apply PVCPending state2. Watch claimProvisioner loop3. Read classParameters4. Create diskCloud API call5. Create PVK8s object6. Bind PVC1:1 match7. AttachNode mount8. Pod runsReadyFailure at any step leaves the PVC Pending or the pod ContainerCreating
Dynamic provisioning sequence from PVC apply through provisioner, bind, attach, and pod start

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.
  • reclaimPolicyDelete removes the cloud disk when the PVC is deleted; Retain keeps 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.

BackendAccess modesBest forWatch out for
Cloud block (EBS, PD, Azure Disk)RWOMySQL, PostgreSQL, single-node RedisZone lock-in; use WaitForFirstConsumer
Cloud file (EFS, Filestore, Azure Files)RWXShared uploads, media librariesHigher cost; latency vs local disk
Longhorn / Ceph RBDRWO, some RWXSelf-managed HA clustersOps overhead; monitor replication health
NFS provisionerRWXLegacy shared storage, dev clustersSingle NFS server = single point of failure
local-pathRWODev, CI, single-node testData lost or orphaned when node dies
StorageClass Provisioner DecisionNeed shared writes?NoYesBlock CSIEBS, PD, LonghornFile CSIEFS, NFS, CephFSDB / cache / queueRWO + Retain policyShared uploadsRWX + backup planWrong branch = Pending PVC or split-brain uploads
Decision flow for picking a StorageClass provisioner based on shared access and workload type

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:

  1. storageClassName matches an existing StorageClass (case-sensitive).
  2. The provisioner pod is running: kubectl get pods -n kube-system | grep csi.
  3. Access mode is supported by the backend.
  4. 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.

Common StorageClass GotchasWrong storageClassNamePVC Pending foreverHelm value typo or missing classDelete reclaim in prodPVC delete wipes diskNo backup, no recoveryImmediate bind + zonesPod cannot attach volumeUse WaitForFirstConsumerRWX on block storeUnsupported access modePick file or object storageValidate StorageClasses in CI before deploy
Frequent StorageClasses and Dynamic Provisioning mistakes that cause Pending PVCs or accidental data deletion

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 WaitForFirstConsumer for zone-local block storage; use RWX-capable provisioners only when multiple pods must write the same path.
  • Set reclaimPolicy: Retain on production database classes to prevent accidental disk deletion with the PVC.
  • Pin storageClassName explicitly 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

A StorageClass is a cluster-scoped template describing how storage is provisioned — provisioner type, performance tier, filesystem, and reclaim behavior. Dynamic provisioning lets a PersistentVolumeClaim trigger automatic PersistentVolume creation through a CSI driver without pre-creating disks.

Static provisioning requires an admin to create PersistentVolumes ahead of time; PVCs bind to matching free PVs. Dynamic provisioning inverts that flow: the PVC carries a storageClassName and the provisioner creates and binds a PV automatically.

Use Retain for production databases. Delete suits ephemeral dev namespaces where wiping disks on namespace delete is acceptable.

A PVC with storageClassName, access mode, and size is applied and enters Pending. The external-provisioner sidecar detects it, reads the StorageClass fields, calls the cloud or local API to create a disk, Kubernetes creates a PersistentVolume object, the PVC binds, and the scheduler can attach the volume to a node. Run kubectl describe pvc if binding stalls; events usually show provisioner errors clearly.

Check four things first: storageClassName matches an existing class exactly (case-sensitive), the CSI provisioner pod is running in kube-system, the requested access mode is supported by the backend, and cloud IAM permissions allow disk creation. On dynamic classes, "no persistent volumes available" often means the provisioner is down or misnamed. I've seen Laravel file-upload pods stuck Pending because no default class existed and the Helm chart never set storageClassName.

Clusters mark one StorageClass as default using the annotation storageclass.kubernetes.io/is-default-class: "true". PVCs that omit storageClassName inherit that class. Only one default should exist; duplicates cause confusing behavior across namespaces. Managed clusters on EKS, GKE, or AKS often ship a default pre-installed. Self-managed Ubuntu clusters need explicit setup or claims may never bind.

Immediate binding provisions and binds as soon as the PVC is created. WaitForFirstConsumer delays provisioning until a pod is scheduled, creating the volume in the same availability zone as the node. Use WaitForFirstConsumer for zone-local block 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 — a real issue for Nepal-hosted workloads on Mumbai region clusters.

ReadWriteOnce suits MySQL, PostgreSQL, and single-node Redis persistence — standard block storage pattern. ReadWriteMany is needed when multiple Laravel queue workers share upload storage and requires NFS, CephFS, EFS, or similar. The StorageClass does not grant access modes by itself; the provisioner must support them. Claiming ReadWriteMany against AWS EBS fails at bind time because EBS supports ReadWriteOnce only.

Define a StorageClass with provisioner set to ebs.csi.aws.com (modern CSI) matching your installed driver exactly. Set driver-specific parameters such as type gp3, iops, throughput, and encrypted true. Set reclaimPolicy to Retain for production databases, volumeBindingMode to WaitForFirstConsumer for zone-local disks, and allowVolumeExpansion true if you expect database growth. Apply a PVC referencing that storageClassName and verify status moves from Pending to Bound with kubectl get pvc.

Cloud block storage (EBS, GCP PD, Azure Disk) suits MySQL and PostgreSQL with ReadWriteOnce. Cloud file storage (EFS, Filestore, Azure Files) suits shared uploads but costs more with higher latency than local disk. Longhorn or Ceph RBD works for self-managed HA clusters with ops overhead. NFS provisioner gives ReadWriteMany for legacy shared storage but a single NFS server is a single point of failure. local-path is fine for dev or CI only — data is lost or orphaned when the node dies.

Three patterns cause most incidents. Deleting a namespace cascades to PVCs; with reclaimPolicy Delete, production MySQL disks disappear in one command. Staging and production should use differently named classes — gp3-staging with Delete and gp3-production with Retain prevents a values-file copy error from pointing at production disks. Relying on cluster defaults in Helm charts is risky because platform teams change defaults during CSI driver upgrades. Pin storageClassName explicitly in production values files.

Enable allowVolumeExpansion true on the StorageClass; the underlying CSI driver must support expansion. After increasing the PVC request, the filesystem inside the pod may still need resize2fs or xfs_growfs manually. Some CSI drivers handle filesystem resize automatically — verify for your driver before relying on it in production. Pair expansion capability with monitoring alerts on disk usage above eighty percent on database volumes.

Charts for MySQL, PostgreSQL, or Redis accept persistence.storageClass values — pin the class name in your values file and do not rely on the cluster default in production. 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 across environments.

WordPress on Kubernetes is uncommon for small Nepali business sites. WooCommerce 11.1 on a single VPS with proper backups is often cheaper and simpler to maintain. StorageClasses earn their complexity when you need horizontal pod scaling, zero-downtime deploys, or multi-AZ database failover. Match the tool to the scale rather than adding cluster storage layers a brochureware shop does not need.

Legal-tech portals with client document sharing often benefit from S3-compatible object storage rather than ReadWriteMany PVCs. Object storage scales cheaper, avoids file-lock issues across multiple pods, and simplifies backup and compliance discussions. On projects like Mijar Law Associates, separating blob storage from relational data made audit conversations clearer. Use StorageClasses for database persistence and Redis AOF files; use buckets for PDFs and scanned documents.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: