Kubernetes PV reclaim policies: Retain, Delete, Recycle in practice

Deleting a PVC does not always delete the underlying storage. If the PV enters Released state and the cloud bill keeps growing, or the application fails after redeployment because the PV is still bound to a deleted claim, the reclaim policy is misaligned with operator intent. This guide explains what happens when a bound PVC is deleted under each reclaim policy, how to recover a Retained volume, why Delete can orphan cloud resources, and why Recycle should not be used in modern clusters. The focus is on practical checks, CSI-specific gotchas, and preventing data loss.

What this means

When a PVC is deleted, the Kubernetes PV controller evaluates the bound PV’s persistentVolumeReclaimPolicy. The policy determines whether the backing storage asset and the PV object survive.

Retain: The PV transitions to Released. Both the PV object and the backing storage asset persist. Data remains intact. The PV is not available for a new claim until an admin clears claimRef from the PV spec and recreates a PVC that references it by name. The admin must also manually delete the backing storage asset from the cloud provider when ready.

Delete: The PV controller triggers deletion of the backing storage asset via the storage provisioner. For CSI volumes, this means the ControllerDeleteVolume RPC. Both the PV object and the cloud resource are removed automatically.

Recycle: This policy is deprecated. It runs rm -rf /thevolume/* on the volume to scrub data, then returns the PV to the Available pool. As of Kubernetes 1.36, only nfs and hostPath volume plugins support Recycle. CSI volumes have no Recycle path at all.

Dynamically provisioned volumes inherit their reclaimPolicy from the StorageClass. Most StorageClasses default to Delete. Static PVs have their own spec.persistentVolumeReclaimPolicy field set at creation time, and this field overrides any StorageClass-level policy.

There are important version differences. Before Kubernetes 1.31, if the PV was deleted before its bound PVC, the reclaim policy was ignored. The PV object vanished but the backing storage asset remained in the cloud, causing orphaned resources. Kubernetes 1.31 introduced deletion-protection finalizers as a beta feature, and 1.33 graduated them to GA. On 1.33+, PVs with Delete policy are reliably cleaned up regardless of whether the PV or PVC is deleted first.

Common causes

CauseWhat it looks likeFirst thing to check
StorageClass defaults to DeletePVC deletion wipes data that operators expected to keepkubectl get storageclass <name> -o jsonpath='{.reclaimPolicy}'
Static CSI PV missing provisioner annotationPV deleted but EBS or EFS volume remains in the cloud consolekubectl get pv <name> -o jsonpath="{.metadata.annotations['pv.kubernetes.io/provisioned-by']}"
Volume still attached to a nodePV stuck in Terminating; CSI driver reports attachment errorkubectl get volumeattachment
Retained PV claimRef not clearedApplication redeploys but cannot bind because claimRef still points to the deleted PVCkubectl get pv <name> -o jsonpath='{.spec.claimRef}'
Recycle policy on unsupported volume typePV stays in Released or fails to transition because no recycler existsPV persistentVolumeReclaimPolicy and volume plugin type
Namespace deletion with Retain policyNamespace and PVC are gone, PV is Released, but cloud storage accrues costCloud console for orphaned resources

Quick checks

# Check PV reclaim policy and current phase
kubectl get pv <name> -o jsonpath='{.spec.persistentVolumeReclaimPolicy}{"\t"}{.status.phase}{"\n"}'

# Verify StorageClass reclaim policy for dynamically provisioned volumes
kubectl get storageclass <name> -o jsonpath='{.reclaimPolicy}'

# Verify static CSI PV has the provisioner annotation
kubectl get pv <name> -o jsonpath="{.metadata.annotations['pv.kubernetes.io/provisioned-by']}"

# Check deletion-protection finalizers on PV (Kubernetes 1.31+)
kubectl get pv <name> -o jsonpath='{.metadata.finalizers}'

# Check if volume is still attached to a node
kubectl get volumeattachment | grep <pv-name>

# Find all Released PVs that may need manual cleanup
kubectl get pv --field-selector=status.phase=Released

# Look for provisioning or attach errors in events
kubectl get events --field-selector reason=ProvisioningFailed
kubectl get events --field-selector reason=FailedAttachVolume

How to diagnose it

  1. Identify the PV bound to the deleted PVC. Check kubectl get pv and match the PV name that was listed in the PVC’s spec.volumeName.
  2. Check the PV phase. Released means Retain. Terminating means Delete is in progress. Failed means reclamation encountered an error.
  3. Read the PV’s spec.persistentVolumeReclaimPolicy. For dynamically provisioned volumes, confirm this matches the originating StorageClass or check if it was patched separately.
  4. If the policy is Delete and the PV is stuck in Terminating, check VolumeAttachment objects. A volume still attached to a node blocks CSI ControllerDeleteVolume. Look for events indicating the volume is still attached.
  5. If the policy is Delete and the backing cloud resource still exists after the PV object is gone on a pre-1.31 cluster, you are seeing the out-of-order deletion leak. The PV controller deleted the object without waiting for the provisioner. On newer clusters with deletion-protection finalizers, verify that the external-provisioner finalizer is present on the PV.
  6. For static CSI PVs with Delete policy, verify the pv.kubernetes.io/provisioned-by annotation. Without it, the external-provisioner skips deletion entirely and the backing resource orphans silently.
  7. If the policy is Retain and you need to reuse the PV, plan for manual recovery. Patch claimRef to null and create a new PVC with volumeName set to the PV name.
flowchart TD
    A[PVC deleted] --> B{PV reclaim policy}
    B -->|Retain| C[PV phase: Released]
    B -->|Delete| D[Trigger ControllerDeleteVolume]
    B -->|Recycle| E[Deprecated unsupported for CSI]
    C --> F[Admin clears claimRef]
    F --> G[PV: Available]
    G --> H[New PVC binds via volumeName]
    D --> I{Volume attached?}
    I -->|Yes| J[PV: Terminating stuck]
    I -->|No| K[Backing storage deleted]
    K --> L[PV object deleted]

Metrics and signals to monitor

SignalWhy it mattersWarning sign
PV phase count in ReleasedReleased PVs indicate manual cleanup debt or unexpected Retain behaviorNumber of Released PVs increasing over time
PV phase count in TerminatingTerminating PVs that do not complete suggest attachment or provisioner blockersPVs stuck in Terminating longer than 5 minutes
StorageClass reclaimPolicyDetermines default behavior for all new dynamic volumesDelete set on a class used for irreplaceable stateful data
CSI provisioner annotation on static PVsMissing annotation causes silent orphaning under Delete policyStatic PV with Delete policy but no pv.kubernetes.io/provisioned-by annotation
VolumeAttachment objectsStuck attachments block Delete reclamationVolumeAttachment exists for a PV that should already be deleted
Cloud storage costsOrphaned resources from failed Delete policy show up as unexpected spendUnexplained growth in unattached disk costs
PVC deletion event rateCorrelate PVC deletions with PV state transitionsPVC deleted but PV does not reach expected end state within the sync window

Fixes

If the cause is an unexpected Delete policy

Patch the PV to Retain before deleting the PVC. This is a safe, read-only change to the PV spec that prevents automatic deletion of the backing asset.

kubectl patch pv <name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

Then delete the PVC. The PV will become Released and the data will persist.

If the cause is a stuck Terminating PV

A volume still attached to a node prevents the CSI driver from issuing DeleteVolume. Wait for the pod to fully terminate and for the VolumeAttachment to clear.

Warning: Force-detaching a volume from a running node through the cloud provider console or API can cause data corruption. Only force-detach if the node is fully terminated or unrecoverable. Once detached, the PV should complete deletion.

If the cause is a Retained PV that must be reused

Patch the PV to clear the claim reference, which allows rebinding.

kubectl patch pv <name> -p '{"spec":{"claimRef": null}}'

Then create a new PVC with volumeName: <pv-name> in its spec. The PVC will bind to the existing PV and the data will be accessible again.

If the cause is a missing CSI annotation on a static PV

If a static PV with Delete policy has already orphaned its backing storage because the annotation was missing, you must manually delete the cloud resource. For new static PVs, always add the pv.kubernetes.io/provisioned-by annotation naming the correct CSI driver at creation time.

If the cause is a StorageClass default you want to change

Edit the StorageClass reclaimPolicy field. This affects only future dynamically provisioned volumes. Existing PVs must be patched individually if you need to change their policy.

Prevention

  • Set StorageClass defaults intentionally. Do not assume the default is Retain. Most distributions and cloud StorageClasses default to Delete.
  • Treat Retain as the safer default for any volume containing data you cannot regenerate. Change the StorageClass or patch individual PVs before PVC deletion.
  • For static CSI PVs using Delete, verify the pv.kubernetes.io/provisioned-by annotation at creation time. Without it, you have a silent orphaning risk.
  • Monitor Released PVs. A growing count means manual cleanup is not keeping up, or teams are deleting PVCs without understanding the policy.
  • On older clusters without deletion-protection finalizers, be aware of the out-of-order deletion leak. Avoid deleting PVs directly while their PVCs still exist.
  • Do not use Recycle. It is deprecated and unsupported for CSI volumes. Use dynamic provisioning with Retain or Delete instead.
  • Document your recovery runbook for Retained volumes. Operators need to know the exact steps to clear claimRef and recreate PVCs.

How Netdata helps

Netdata monitors node disk utilization and mount state. Use these metrics to correlate storage pressure with volume attachment events or to detect unexpected disk consumption from Retained volumes that are still mounted.