# PVC Has No StorageClass

> Flags PersistentVolumeClaims with an empty or missing storageClassName, which depend on cluster defaults or manual volumes to bind.

Source: https://zop.dev/integrations/kubernetes/recommendations/pvc-has-no-storageclass

---

## What an unnamed storage class leaves to chance

The StorageClass named on a claim decides which provisioner creates the disk, which disk type
and which reclaim policy the volume gets. When a claim names none, the outcome depends on cluster
settings the application team usually does not control. The
[PersistentVolumes documentation](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)
spells out the two cases:

- A claim with `storageClassName: ""` can bind only to PersistentVolumes that also have no class.
  If nobody created such a volume, the claim waits forever.
- A claim with the field missing is given the cluster's default StorageClass, when the
  DefaultStorageClass admission plugin is on and a default exists. If none exists, the field stays
  unset until one appears; retroactive assignment of a later default is stable since Kubernetes
  v1.28.

Either way, the disk a team gets can change when a platform team changes the default, and moving
a workload to another cluster can leave its claims pending.

## Listing claims with no class

```bash
kubectl get pvc -A -o json | jq -r '
  .items[]
  | select((.spec.storageClassName // "") == "")
  | "\(.metadata.namespace)/\(.metadata.name) phase=\(.status.phase)"'
```

To see which StorageClass, if any, is the cluster default, run `kubectl get storageclass`; the
default is marked `(default)` in the name column.

## The single condition behind the finding

ZopNight reads the claim's storage class name from its collected spec and fires when the value is
empty or absent. It treats `""` and a missing field the same way. There is no age gate or
metric; the check repeats on each evaluation and clears once the claim names a class. Because the
admission plugin normally fills the field in at creation, a claim that still shows no class was
usually created with `""` on purpose or before a default existed.

## Limits of the check

The rule does not look at the claim's phase, so a Bound claim with no class is flagged alongside
a pending one. Claims stuck in `Pending` for other reasons are covered by
[Unbound PVC](https://zop.dev/integrations/kubernetes/recommendations/unbound-pvc). The rule also cannot see
whether a classless claim was designed for static provisioning, which is the common false positive.

## A governance finding, with no savings figure

This recommendation carries no dollar amount. Its value is predictability: every claim states the
storage it needs rather than inheriting it.

## Naming the class explicitly

1. Run `kubectl get storageclass` and choose the class the workload should use.
2. Set `storageClassName` in the claim template of the Deployment, StatefulSet or Helm chart that
   creates the claim.
3. For claims already bound, note that the field cannot simply be edited on a live claim; the new
   value applies to newly created claims.
4. For static provisioning, keep `""` and document it, or give the pre-created volumes and claims
   a named class so the intent is explicit.
5. Make sure exactly one StorageClass carries the `storageclass.kubernetes.io/is-default-class`
   annotation set to `true`.
