# Unbound PVC

> A PersistentVolumeClaim in Pending state has requested storage that was never provisioned, so any pod mounting it cannot start. ZopNight surfaces these as orphans. There are 4 usual causes, and the kubectl describe output carries the provisioner error that names which one applies.

Source: https://zop.dev/integrations/kubernetes/recommendations/unbound-pvc
Updated: 2026-08-19

---

## A pending claim blocks a pod, silently

A pod mounting an unbound PVC does not fail. It stays `Pending` indefinitely, because the
scheduler cannot place a pod whose volume does not exist yet.

That failure mode is quiet: no crash loop, no restart count climbing, no error in the container
log. The container never started. Teams often find these only when someone asks why a
deployment has been at zero ready replicas since last Tuesday.

## Why claims stay unbound

**No default StorageClass**, or a `storageClassName` naming one that does not exist in this
cluster. Extremely common after moving a manifest between clusters.

**Zone mismatch.** With `WaitForFirstConsumer` binding, the volume is created in the zone of the
node the pod lands on. If node affinity forces the pod somewhere the storage class cannot
provision, neither side resolves.

**Quota exhausted.** A namespace `ResourceQuota` limiting `requests.storage` or
`persistentvolumeclaims` blocks new claims once reached, and the message lives on the PVC rather
than anywhere obvious.

**Capacity unavailable** in the requested zone for that volume type.

## The cost angle, such as it is

An unbound PVC provisions nothing, so it costs nothing directly. This is a reliability finding
wearing an orphan label.

What it can indicate is money: a namespace full of pending claims usually means a workload was
half-migrated, and the volumes it *did* successfully create elsewhere may now be orphaned and
billing.

## Describing a Pending PVC for the provisioner error

```bash
kubectl get pvc -A --field-selector status.phase=Pending
kubectl describe pvc <name> -n <namespace> | tail -20
```

The `describe` output carries the provisioner's actual error, which is where the real answer
lives. The phase alone tells you nothing about the cause.
