πŸŽ‰ DevOps Interview Prep Bundle is live β€” 1000+ Q&A across 20 topicsGet it β†’
All Articles

Kubernetes 1.37: Find Unused PVCs Without Custom Scripts

Use the Kubernetes 1.37 Unused PVC condition and lastTransitionTime to find orphaned storage safely across namespaces.

DevOpsBoys3 min read
Share:Tweet

PersistentVolumeClaims are deliberately hard to delete by accident. That protects data, but it also means development clusters slowly collect claims that no running workload uses. Kubernetes 1.37 makes those claims easier to identify with the Beta PersistentVolumeClaimUnusedSinceTime feature.

The feature is enabled by default in Kubernetes 1.37. The PVC protection controller adds an Unused condition to each claim, so you no longer need to cross-reference every Pod and PVC just to answer: β€œIs any non-terminal Pod using this claim?”

What the Unused condition means

The controller reports:

ConditionMeaning
Unused=TrueNo non-terminal Pod currently references the PVC
Unused=FalseAt least one non-terminal Pod references the PVC

A Pending Pod still counts as using the claim because it expresses an intent to mount it. Pods in Succeeded or Failed do not count. When the final non-terminal Pod stops referencing the claim, lastTransitionTime records when the PVC became unused.

This signal is useful, but it is not permission to delete data. A StatefulSet scaled to zero, a disaster-recovery volume, or a claim retained for an audit may correctly be unused.

Inspect one PVC

bash
kubectl get pvc app-data -n production -o jsonpath='{.status.conditions}' | jq

An unused claim looks similar to:

json
{
  "type": "Unused",
  "status": "True",
  "reason": "NoPodsUsingPVC",
  "lastTransitionTime": "2026-08-01T10:30:00Z"
}

Confirm that no Pod references it:

bash
kubectl get pods -A -o json | jq -r '
  .items[]
  | select(any(.spec.volumes[]?; .persistentVolumeClaim.claimName == "app-data"))
  | "\(.metadata.namespace)/\(.metadata.name) \(.status.phase)"
'

The second check is valuable before deletion because it exposes Pending workloads and references outside the namespace you expected.

List unused PVCs across the cluster

bash
kubectl get pvc -A -o json | jq -r '
  .items[]
  | .status.conditions[]? as $condition
  | select($condition.type == "Unused" and $condition.status == "True")
  | [
      .metadata.namespace,
      .metadata.name,
      .spec.resources.requests.storage,
      $condition.lastTransitionTime
    ]
  | @tsv
'

To find claims unused for more than 30 days:

bash
kubectl get pvc -A -o json | jq -r '
  .items[]
  | select(.status.conditions[]? | select(.type == "Unused" and .status == "True"))
  | (.status.conditions[] | select(.type == "Unused")) as $unused
  | select((now - ($unused.lastTransitionTime | fromdateiso8601)) > (30 * 86400))
  | "\(.metadata.namespace)/\(.metadata.name) unused since \($unused.lastTransitionTime)"
'

Build a safe review workflow

Do not pipe this query into kubectl delete. Use it to create a review queue:

  1. Label the claim with an owner, application, environment, and retention policy.
  2. Check whether a StatefulSet, CronJob, backup process, or paused environment expects it.
  3. Record the StorageClass and the PersistentVolume reclaim policy.
  4. Take and verify a snapshot when policy requires one.
  5. Require an owner approval for production claims.
  6. Delete in a non-production environment first and confirm the application can recover.

Inspect the backing PV before making a decision:

bash
PV=$(kubectl get pvc app-data -n production -o jsonpath='{.spec.volumeName}')
kubectl get pv "$PV" -o custom-columns='NAME:.metadata.name,CLASS:.spec.storageClassName,RECLAIM:.spec.persistentVolumeReclaimPolicy,STATUS:.status.phase'

The reclaim policy determines what happens to the backing storage after the claim is deleted. Treat that as a separate destructive decision.

Monitor rather than auto-delete

A safer automation exports the condition as inventory and opens a ticket after a threshold. Useful fields include namespace, claim, requested capacity, StorageClass, owner label, lastTransitionTime, PV name, and reclaim policy.

That approach turns the native condition into a cost-control signal without allowing one script bug to erase storage.

Compatibility notes

  • The feature is Beta and enabled by default in Kubernetes 1.37.
  • Older clusters may not populate the Unused condition.
  • The condition tracks Pod references, not business value, backup requirements, or application ownership.
  • Pending Pods count as users; terminal Pods do not.

Preparing for Kubernetes operations or the CKA exam? Explore this hands-on CKA preparation course on Udemy. Affiliate link: DevOpsBoys may earn a commission at no extra cost to you.

Sources

πŸ”§

Today I Fixed

Short real fixes from production β€” posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam β€” just practical engineering content.

Related Articles

Comments