Resize a Kubernetes PVC Without Downtime (StatefulSets Included)
DevOps Engineering

Resize a Kubernetes PVC Without Downtime (StatefulSets Included)

Expand a Kubernetes PersistentVolumeClaim online, resize StatefulSet volumes, and fix FileSystemResizePending.

Michael Zion

10 min read

To expand a PersistentVolumeClaim without taking the workload offline, confirm the StorageClass has allowVolumeExpansion: true and the CSI driver supports online expansion, then raise spec.resources.requests.storage on the PVC. The driver grows the volume and the kubelet grows the filesystem while the pod keeps running. Verify with df -h inside the pod.

kubectl patch pvc data-postgres-0 -n production \
  -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'

The command is one line. The safe part is proving every layer supports the change, handling StatefulSets (whose volumeClaimTemplates cannot be edited in place), and keeping GitOps from fighting you. This guide covers all three.

Every layer a PVC resize touches

LayerWhat must be trueHow to check
StorageClassallowVolumeExpansion: truekubectl get sc STORAGECLASS -o jsonpath='{.allowVolumeExpansion}'
CSI driverSupports expansion, ideally online (while attached)Driver docs; kubectl get csidriver
Cloud volumeWithin provider limits (for example, an EBS volume can be modified only once every 6 hours)Provider console or API
Filesystemext4 or XFS grow online; nothing shrinkskubectl exec POD -- df -h MOUNT
Source of truthGit, Helm values or Terraform show the new sizeYour repo and the next sync diff

What has to support online expansion

A PVC resize touches several layers. Kubernetes accepts the request, the storage driver expands the underlying volume, the node makes the new size available to the pod, and the filesystem grows inside the mounted volume. Any weak link can turn a simple storage change into an incident.

Four-step diagram of a PVC resize: Kubernetes accepts the request when the StorageClass sets allowVolumeExpansion: true, the CSI driver expands the volume online or offline only, the node makes the new size available, and the ext4 or XFS filesystem grows but never shrinks.
A resize is only as safe as the weakest of these four layers.

Before resizing a production PVC, check these requirements:

  • The StorageClass allows expansion. The StorageClass must set allowVolumeExpansion: true. Without this, Kubernetes rejects the PVC update.
  • The CSI driver supports expansion. The driver needs to support volume expansion for the storage backend you use. Some drivers support offline expansion only, while others support expansion while the volume is attached.
  • The filesystem supports online growth. Common Linux filesystems such as ext4 and XFS support online expansion in typical Kubernetes setups. Shrinking is a different problem and is not supported through PVC resize.
  • The workload can keep running during the change. A stateless pod with a cache behaves differently from a single-writer database. Your application should tolerate the storage layer changing size while the mount stays active.
  • Your delivery process will not revert the patch. Helm, Kustomize, Terraform, Pulumi, Argo CD, Flux, or another controller may re-apply the old value if your desired state is stale.

If you are still standardizing your cluster patterns, it is worth treating this as part of broader Kubernetes platform design rather than a one-off emergency task. Storage classes, backup policy, pod disruption budgets, and deployment automation all affect how safe this operation feels under pressure.

Preflight checks before you touch the PVC

Start by identifying the PVC, StorageClass, PV, pod, node, and application owner. The worst time to discover that a volume backs a primary database is after you have already changed production state.

Use these checks before making the resize:

  1. Confirm current usage. Check filesystem usage from inside the pod with df -h, and compare it with application-level metrics if you have them.
  2. Check the PVC and PV. Review requested size, bound volume, access mode, volume mode, and StorageClass.
  3. Check the StorageClass. Confirm allowVolumeExpansion is enabled.
  4. Check driver behavior. Read the CSI driver documentation for online versus offline expansion and any provider-specific limits.
  5. Check backups and recovery. Make sure a recent backup or snapshot exists and that the team knows how to restore it.
  6. Check automation drift. Find the source of truth for the PVC size before changing it. If Git says 100Gi and the cluster says 200Gi, your next sync may undo expectations even if it cannot shrink the actual volume.

These checks are especially important in multi-cluster environments where storage classes may share names but behave differently. Teams that manage many production clusters often need consistent infrastructure patterns, as shown in work to simplify AWS and Kubernetes infrastructure management.

Resize the PVC safely

For a supported online expansion, the operational flow is simple: update the PVC storage request to a larger value, watch Kubernetes reconcile the change, then verify the filesystem inside the pod sees the new size.

First, inspect the PVC:

kubectl get pvc -n production
kubectl describe pvc data-postgres-0 -n production

Then patch the PVC with the new requested size. You can only increase the value.

kubectl patch pvc data-postgres-0 \
  -n production \
  -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'

Watch the PVC status and events:

kubectl get pvc data-postgres-0 -n production -w
kubectl describe pvc data-postgres-0 -n production

Depending on the driver and Kubernetes version, you may see conditions such as FileSystemResizePending. That usually means the underlying volume has expanded, but the filesystem expansion still needs to complete on the node. With online expansion support, this often resolves while the pod keeps running. With offline-only behavior, you may need to restart the pod or let a controlled reschedule happen.

Flow chart: kubectl patch pvc leads to the volume being expanded and the FileSystemResizePending condition, meaning the filesystem has not grown yet; with online expansion it clears while the pod keeps running, with an offline only driver you restart the pod in a controlled way, and both paths end with verifying df -h inside the pod.
The driver decides whether the pending condition clears on its own or needs a pod restart.

Finally, verify from inside the running pod:

kubectl exec -n production postgres-0 -- df -h

The PVC request, PV capacity, and filesystem size should all match your intended state or be in the expected reconciliation path. If the application exposes disk metrics, confirm those too. A clean Kubernetes object state does not always prove the application can use the added space.

StatefulSets, GitOps, and desired state traps

Stateful workloads add a few practical wrinkles. A StatefulSet often creates PVCs from volumeClaimTemplates, but existing PVCs are separate objects after creation. In many Kubernetes versions and setups, you cannot simply update the StatefulSet template and expect existing claims to resize. You usually patch each PVC directly.

For example, a database StatefulSet with three replicas may have PVCs like:

  • data-postgres-0
  • data-postgres-1
  • data-postgres-2

If all three need more space, resize each PVC intentionally. Do not assume that changing a chart value will update existing PVCs. It may only affect new claims, or it may fail because the StatefulSet field is immutable.

Workaround: resize StatefulSet volumes and keep the template in sync

Editing volumeClaimTemplates on a live StatefulSet fails with spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden. The standard workaround keeps the pods running:

  1. Patch every existing PVC to the new size and wait until each pod sees it:
    for i in 0 1 2; do
      kubectl patch pvc data-postgres-$i -n production \
        -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
    done
  2. Delete only the StatefulSet object, leaving its pods and PVCs in place:
    kubectl delete statefulset postgres -n production --cascade=orphan
  3. Change the size in volumeClaimTemplates in your manifest or chart values and re-apply it. The new StatefulSet adopts the running pods, and future replicas get the larger size.

While the StatefulSet object is gone, nothing recreates a pod that dies, so do steps 2 and 3 back to back. If Argo CD or Flux manages the StatefulSet, pause auto-sync first so it does not recreate the object with the old template.

Three-step StatefulSet volume resize: patch every PVC (data-postgres-0, data-postgres-1, data-postgres-2) to 200Gi, delete only the StatefulSet with --cascade=orphan so pods and PVCs stay, then re-apply a larger volumeClaimTemplates that adopts the running pods; do steps 2 and 3 back to back and pause Argo CD or Flux auto-sync first.
Pods and PVCs keep running while the StatefulSet object is briefly replaced.

GitOps and infrastructure as code add another layer. If your PVCs are declared in Git, update the source of truth before or immediately after the live patch. If your PVCs were created by a Helm chart, check whether the chart manages PVC resources directly or only the StatefulSet template. If your platform uses Pulumi, Terraform, or another tool, make sure the tool understands the live resize instead of treating it as drift. Large cluster migrations and imports, such as efforts to import high-scale Kubernetes clusters into Pulumi, often expose these ownership problems.

A practical pattern is to record the resize in the same place your team records production changes:

  • The old and new PVC size
  • The namespace, PVC name, workload, and cluster
  • The StorageClass and CSI driver
  • The commands or pull request used
  • The validation result after expansion

Failure modes to plan for

Most PVC expansions are uneventful when the storage stack supports them. The failures tend to come from assumptions, not from the patch command itself.

The StorageClass does not allow expansion

Kubernetes will reject the update if allowVolumeExpansion is missing or false. You can update the StorageClass for future attempts, but you should confirm that the underlying driver and storage backend support expansion before changing it.

The volume grows but the filesystem does not

If the PVC shows a larger capacity but the pod still sees the old filesystem size, check PVC conditions and kubelet events on the node. The filesystem resize may be pending until the pod restarts, especially with drivers that do not support online filesystem expansion.

The application still reports low disk space

Some applications cache disk checks or write to a different path than the mounted PVC. Confirm the mount path inside the container. For example, a pod may mount the PVC at /var/lib/postgresql/data, while an alert checks the container root filesystem.

A controller reverts or fights the change

Automation may re-apply old manifests. Even when Kubernetes does not shrink the volume, your configuration can become misleading. Update your declared state so future operators do not see conflicting values.

The storage backend hits a provider limit

Cloud disks, network file systems, and managed storage services have their own limits for maximum size, expansion frequency, performance tiers, and attachment behavior. Kubernetes will surface some errors in events, but the real cause may live in the provider API.

Mapping of PVC resize failure modes to where to look: PVC update rejected to allowVolumeExpansion on the StorageClass; volume grew but filesystem did not to PVC conditions and kubelet events; app still reports low disk to the mount path inside the container; controller reverts the change to your declared state in Git; backend hits a limit to the provider API.
Each symptom points to a different layer, so check the right one first.

Managed platforms can reduce some operational burden, but they do not remove the need to understand the storage path. If you run workloads on Azure Kubernetes Service, Amazon Elastic Kubernetes Service, Google Kubernetes Engine, or self-managed clusters, the same basic checks still apply: StorageClass, CSI driver, filesystem, workload, and source of truth.

A safe operating pattern for production

For production systems, treat PVC expansion as a small change with a real runbook. You do not need a long maintenance window for every resize, but you do need a repeatable path.

A solid runbook looks like this:

  1. Open a change record or pull request. Include the target PVC, current size, new size, reason, and rollback constraints.
  2. Confirm backup status. PVC expansion is one-way at the Kubernetes level. If you choose the wrong size or expose a storage bug, restore may be your cleanest recovery path.
  3. Validate support in a lower environment. Use the same StorageClass and workload pattern where possible.
  4. Patch the PVC during a low-risk window. You may not need downtime, but you still want people available if the driver behaves differently than expected.
  5. Watch PVC events and pod logs. Do not stop at a successful kubectl patch.
  6. Verify inside the pod. Check the mounted filesystem and application health.
  7. Update desired state and documentation. Make the new size visible to the next person who touches the service.

This is the same mindset you need for other stateful platform work. In data-heavy systems, such as pipelines built with Apache Airflow, Kubernetes, cloud infrastructure, and Terraform, storage changes sit close to application reliability. A reference architecture for scalable biotech cloud infrastructure shows how orchestration, Kubernetes, and infrastructure code need to line up for dependable operations.

FAQ

Can I resize a PVC without restarting the pod?

Yes, if the CSI driver supports online expansion. Most current cloud drivers (AWS EBS, Google Persistent Disk, Azure Disk) do for ext4 and XFS. If the PVC shows the FileSystemResizePending condition and it does not clear, the driver needs the volume remounted, so restart the pod in a controlled way.

Can I shrink a PVC?

No. Kubernetes rejects a smaller storage request. To shrink, create a new, smaller PVC, copy the data (for example with a one-off Job running rsync or a database dump and restore), switch the workload over, then delete the old claim.

How do I resize the volumes of a StatefulSet?

Patch each PVC created from the template directly, then delete the StatefulSet with --cascade=orphan and re-apply it with the larger volumeClaimTemplates size. The steps are in the StatefulSet section above.

What does FileSystemResizePending mean?

The underlying volume has grown, but the filesystem on the node has not been expanded yet. With online expansion it clears on its own within a kubelet sync; with offline-only drivers it clears after the pod restarts.

Why is my PVC larger but df still shows the old size?

Usually the filesystem resize is still pending, or you are checking the wrong mount path. Run df -h on the PVC mount path inside the pod, and check kubectl describe pvc for conditions and events. The official reference is the Kubernetes docs on expanding persistent volume claims.

Takeaway

Resizing a Kubernetes PVC without downtime is realistic when the full path supports online expansion. Do the preflight checks, increase the PVC request, watch Kubernetes events, verify the filesystem from inside the pod, and update your source of truth. The command is small. The reliability comes from knowing which layer owns each part of the resize and checking the result before you move on.