---
# === IDENTITY ===
id: software/devops/k8s-persistent-volumes/2026
canonical_question: "Kubernetes reference: Persistent Volume Claims"
aliases:
  - "Kubernetes PersistentVolumeClaim PVC reference"
  - "how to use persistent storage in Kubernetes"
  - "Kubernetes PV and PVC explained"
  - "k8s StorageClass dynamic provisioning"
  - "Kubernetes volume access modes RWO RWX"
  - "how to expand a PVC in Kubernetes"
  - "Kubernetes VolumeSnapshot and PVC cloning"
  - "StatefulSet volumeClaimTemplates"
entity_type: software_reference
domain: software > devops > Kubernetes Persistent Volume Claims
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.94
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Kubernetes 1.31: PV deletion protection finalizers GA (2024-08); Kubernetes 1.29: ReadWriteOncePod GA (2023-12)"
  next_review: 2026-08-26
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "A PVC can bind to exactly ONE PV -- the binding is one-to-one and exclusive until the PVC is deleted"
  - "Volume expansion requires allowVolumeExpansion: true on the StorageClass -- PVCs using classes without this flag CANNOT be resized"
  - "Recycle reclaim policy is DEPRECATED since Kubernetes 1.22 -- use dynamic provisioning with Delete or Retain instead"
  - "VolumeSnapshot requires the CSI driver to implement snapshot capability AND the snapshot-controller + CRDs to be installed cluster-wide"
  - "NEVER delete a PVC while a Pod is actively writing to the volume -- Storage Object in Use Protection defers deletion but data corruption can still occur during ungraceful termination"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Running a StatefulSet with per-replica persistent storage and need help with headless services, ordering, and operators"
    use_instead: "software/devops/k8s-statefulset-databases/2026"
  - condition: "Using ephemeral storage (emptyDir, configMap, secret) that does not persist beyond Pod lifetime"
    use_instead: "Use Kubernetes volumes documentation: https://kubernetes.io/docs/concepts/storage/volumes/"
  - condition: "Deploying stateless applications without persistent data"
    use_instead: "software/devops/k8s-deployment-service-ingress/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "storage_backend"
    question: "What storage backend or cloud provider are you using?"
    type: choice
    options: ["AWS EBS", "GCP PD", "Azure Disk", "NFS", "Ceph/Rook", "Local", "Other CSI"]
  - key: "access_pattern"
    question: "How many nodes/pods need simultaneous access to the volume?"
    type: choice
    options: ["Single pod (RWOP)", "Single node (RWO)", "Multiple nodes read-only (ROX)", "Multiple nodes read-write (RWX)"]
  - key: "provisioning_model"
    question: "Do you want volumes created automatically or pre-provisioned by an admin?"
    type: choice
    options: ["Dynamic (StorageClass)", "Static (pre-created PV)", "Unsure"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/devops/k8s-persistent-volumes/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "software/devops/k8s-deployment-service-ingress/2026"
      label: "Kubernetes Deployment, Service & Ingress Basics"
  related_to:
    - id: "software/devops/k8s-statefulset-databases/2026"
      label: "Kubernetes StatefulSet for Databases"
    - id: "software/devops/k8s-hpa/2026"
      label: "Kubernetes Horizontal Pod Autoscaler"
  solves: []
  alternative_to: []
  often_confused_with: []

# === SOURCES (8 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Persistent Volumes"
    author: Kubernetes
    url: https://kubernetes.io/docs/concepts/storage/persistent-volumes/
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src2
    title: "Dynamic Volume Provisioning"
    author: Kubernetes
    url: https://kubernetes.io/docs/concepts/storage/dynamic-provisioning/
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src3
    title: "Volume Snapshots"
    author: Kubernetes
    url: https://kubernetes.io/docs/concepts/storage/volume-snapshots/
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src4
    title: "Volume Expansion Now A Stable Feature"
    author: Kubernetes
    url: https://kubernetes.io/blog/2022/05/05/volume-expansion-ga/
    type: official_docs
    published: 2022-05-05
    reliability: authoritative
  - id: src5
    title: "Change the Reclaim Policy of a PersistentVolume"
    author: Kubernetes
    url: https://kubernetes.io/docs/tasks/administer-cluster/change-pv-reclaim-policy/
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src6
    title: "Kubernetes CSI Developer Documentation"
    author: Kubernetes CSI
    url: https://kubernetes-csi.github.io/docs/
    type: official_docs
    published: 2025-06-01
    reliability: high
  - id: src7
    title: "GKE Persistent Volumes and Provisioning"
    author: Google Cloud
    url: https://cloud.google.com/kubernetes-engine/docs/concepts/persistent-volumes
    type: official_docs
    published: 2025-12-01
    reliability: high
  - id: src8
    title: "StatefulSets"
    author: Kubernetes
    url: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
---

# Kubernetes Reference: Persistent Volume Claims

## TL;DR

- **Bottom line**: PersistentVolumeClaims (PVCs) are the standard Kubernetes abstraction for requesting durable block or file storage; pair them with a StorageClass for automatic dynamic provisioning so you never pre-create PVs by hand.
- **Key tool/command**: `kubectl get pvc -A` and `kubectl describe pvc <name>` to inspect binding status, capacity, and events.
- **Watch out for**: PVCs stuck in `Pending` -- 90% of the time the cause is a missing or misconfigured StorageClass, an access-mode mismatch, or `WaitForFirstConsumer` binding with no Pod scheduled yet.
- **Works with**: Kubernetes 1.26+ (all features GA); CSI drivers required for snapshots, cloning, and RWOP access mode.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- PVC-to-PV binding is **one-to-one and exclusive** -- once bound, no other PVC can claim that PV until the original PVC is deleted and the PV is released/recycled. [src1]
- Volume expansion requires `allowVolumeExpansion: true` on the StorageClass -- attempting to resize a PVC whose class lacks this flag produces an error. [src4]
- `ReadWriteOncePod` (RWOP) access mode requires Kubernetes 1.29+ and a CSI driver that supports it -- in-tree plugins do not support RWOP. [src1]
- `Recycle` reclaim policy is **deprecated** since Kubernetes 1.22 -- never use it in new deployments; use `Delete` or `Retain` with dynamic provisioning. [src5]
- VolumeSnapshot requires three cluster-wide components: snapshot CRDs, snapshot-controller, and a CSI driver that implements the snapshot interface. [src3]
- Never delete a PVC with `kubectl delete pvc --force` while a Pod is writing -- the `kubernetes.io/pvc-protection` finalizer exists to prevent data loss and bypassing it risks corruption. [src1]

## Quick Reference

| Resource | API Version | Key Fields | Purpose |
|---|---|---|---|
| `PersistentVolume` | `v1` | `spec.capacity.storage`, `spec.accessModes`, `spec.persistentVolumeReclaimPolicy`, `spec.storageClassName` | Cluster-wide storage resource |
| `PersistentVolumeClaim` | `v1` | `spec.accessModes`, `spec.resources.requests.storage`, `spec.storageClassName`, `spec.selector` | Namespace-scoped storage request |
| `StorageClass` | `storage.k8s.io/v1` | `provisioner`, `parameters`, `reclaimPolicy`, `volumeBindingMode`, `allowVolumeExpansion` | Template for dynamic provisioning |
| `VolumeSnapshot` | `snapshot.storage.k8s.io/v1` | `spec.source.persistentVolumeClaimName`, `spec.volumeSnapshotClassName` | Point-in-time volume copy |
| `VolumeSnapshotClass` | `snapshot.storage.k8s.io/v1` | `driver`, `deletionPolicy`, `parameters` | Snapshot provisioning template |
| `VolumeSnapshotContent` | `snapshot.storage.k8s.io/v1` | `spec.source.volumeHandle`, `spec.driver`, `spec.deletionPolicy` | Actual snapshot on storage backend |

| Access Mode | Short | Nodes | Pods | Typical Use Case |
|---|---|---|---|---|
| `ReadWriteOnce` | RWO | 1 node | Any pods on that node | Databases, single-writer apps |
| `ReadOnlyMany` | ROX | Many nodes | Many pods (read-only) | Shared config, static assets |
| `ReadWriteMany` | RWX | Many nodes | Many pods (read-write) | Shared filesystem (NFS, CephFS, EFS) |
| `ReadWriteOncePod` | RWOP | 1 node | Exactly 1 pod | Exclusive writer (etcd, single-leader) |

| Reclaim Policy | Behavior After PVC Deletion | Default For | Recommended Use |
|---|---|---|---|
| `Delete` | PV and underlying storage asset deleted | Dynamic provisioning | Dev/test, ephemeral workloads |
| `Retain` | PV marked `Released`, data preserved | Static provisioning | Production data, compliance |
| `Recycle` (deprecated) | `rm -rf /thevolume/*`, PV reused | Legacy clusters | Never -- use dynamic provisioning |

| Volume Binding Mode | When PV is Bound | Best For |
|---|---|---|
| `Immediate` | As soon as PVC is created | Single-zone clusters, pre-provisioned PVs |
| `WaitForFirstConsumer` | When Pod using PVC is scheduled | Multi-zone clusters (zone-aware provisioning) |

## Decision Tree

```
START: Do you need persistent storage in Kubernetes?
├── Is the data ephemeral (survives Pod restart but not Pod deletion)?
│   ├── YES → Use emptyDir volume (not a PVC)
│   └── NO ↓
├── Do you need storage that survives Pod deletion and rescheduling?
│   ├── YES ↓
│   └── NO → Use emptyDir or configMap/secret volumes
├── Do multiple Pods on different nodes need read-write access?
│   ├── YES → Use RWX with NFS/CephFS/EFS StorageClass
│   └── NO ↓
├── Does exactly one Pod need exclusive write access?
│   ├── YES + K8s 1.29+ → Use RWOP access mode
│   ├── YES + K8s <1.29 → Use RWO access mode
│   └── NO ↓
├── Are you running a StatefulSet with per-replica storage?
│   ├── YES → Use volumeClaimTemplates (see StatefulSet section)
│   └── NO → Create a standalone PVC and reference in Pod spec
├── Is your cluster multi-zone?
│   ├── YES → Set volumeBindingMode: WaitForFirstConsumer
│   └── NO → Immediate binding is fine
└── Do you need snapshots or cloning?
    ├── YES → Verify CSI driver supports snapshots + install snapshot-controller
    └── NO → Standard PVC workflow
```

## Step-by-Step Guide

### 1. Create a StorageClass for dynamic provisioning

Define a StorageClass that tells Kubernetes which CSI provisioner to use and how volumes should behave. Most managed Kubernetes clusters come with a default StorageClass, but creating explicit ones gives you control over performance tiers and reclaim behavior. [src2]

```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com          # AWS EBS CSI driver
parameters:
  type: gp3                            # General Purpose SSD
  fsType: ext4
reclaimPolicy: Retain                  # Keep data after PVC deletion
volumeBindingMode: WaitForFirstConsumer # Zone-aware provisioning
allowVolumeExpansion: true             # Allow PVC resize
```

**Verify**: `kubectl get sc fast-ssd` --> shows the StorageClass with PROVISIONER and RECLAIMPOLICY columns.

### 2. Create a PersistentVolumeClaim

A PVC requests storage from the cluster. With dynamic provisioning, Kubernetes automatically creates a matching PV. [src1]

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi
```

**Verify**: `kubectl get pvc app-data` --> STATUS should show `Bound` (or `Pending` if using `WaitForFirstConsumer` until a Pod is scheduled).

### 3. Mount the PVC in a Pod

Reference the PVC in the Pod's `volumes` section and mount it into a container. [src1]

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: nginx:1.27
      volumeMounts:
        - mountPath: /data
          name: storage
  volumes:
    - name: storage
      persistentVolumeClaim:
        claimName: app-data
```

**Verify**: `kubectl exec app -- df -h /data` --> shows the mounted filesystem with the requested capacity.

### 4. Expand a PVC (online resize)

To increase storage, patch the PVC's `spec.resources.requests.storage` field. Most CSI drivers support online expansion without Pod restart. [src4]

```bash
kubectl patch pvc app-data -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'
```

**Verify**: `kubectl get pvc app-data` --> CAPACITY shows `50Gi`. Check events: `kubectl describe pvc app-data | grep -i resize` --> shows `FileSystemResizeSuccessful`.

### 5. Create a VolumeSnapshot

Take a point-in-time snapshot of an existing PVC. Requires snapshot-controller and CSI driver snapshot support. [src3]

```yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: app-data-snap-20260227
spec:
  volumeSnapshotClassName: csi-aws-snapclass
  source:
    persistentVolumeClaimName: app-data
```

**Verify**: `kubectl get volumesnapshot app-data-snap-20260227` --> READYTOUSE should show `true`.

### 6. Restore a PVC from a snapshot

Create a new PVC with the `dataSource` field pointing to an existing VolumeSnapshot. The new volume is pre-populated with the snapshot data. [src3]

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data-restored
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi
  dataSource:
    name: app-data-snap-20260227
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
```

**Verify**: `kubectl get pvc app-data-restored` --> STATUS shows `Bound` and the data matches the snapshot contents.

### 7. Use volumeClaimTemplates in a StatefulSet

For StatefulSets, each replica automatically gets its own PVC via `volumeClaimTemplates`. PVCs are named `{template-name}-{pod-name}` (e.g., `data-myapp-0`). [src8]

```yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp
spec:
  serviceName: myapp
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myapp:latest
          volumeMounts:
            - mountPath: /data
              name: data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 10Gi
```

**Verify**: `kubectl get pvc -l app=myapp` --> shows `data-myapp-0`, `data-myapp-1`, `data-myapp-2`, all `Bound`.

## Code Examples
<!-- Keep inline examples <=15 lines. For longer scripts, extract to scripts/ subdirectory
     and link: "Full script: [name.ext](scripts/name.ext) (N lines)" -->

### YAML: Static PV with NFS backend

```yaml
# Input:  NFS server at 10.0.0.100, export path /exports/data
# Output: PV available for PVC binding with RWX access
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    server: 10.0.0.100
    path: /exports/data
```

### YAML: PVC with label selector for static binding

```yaml
# Input:  Pre-created PV with label tier=fast
# Output: PVC bound to a specific PV via selector
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: fast-storage
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  storageClassName: ""       # Disable dynamic provisioning
  selector:
    matchLabels:
      tier: fast
```

### YAML: VolumeSnapshotClass for AWS EBS

```yaml
# Input:  AWS EBS CSI driver installed
# Output: Snapshot class for creating EBS snapshots
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: csi-aws-snapclass
driver: ebs.csi.aws.com
deletionPolicy: Retain
```

### Bash: Clone a PVC (volume cloning)

```bash
# Input:  Existing PVC "source-pvc" in namespace "default"
# Output: New PVC "clone-pvc" with identical data
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: clone-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi
  dataSource:
    name: source-pvc
    kind: PersistentVolumeClaim
EOF
```

## Anti-Patterns

### Wrong: Hardcoding storageClassName to empty string unintentionally

```yaml
# BAD -- storageClassName: "" disables dynamic provisioning entirely
# The PVC will never bind unless a matching PV exists with storageClassName: ""
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: ""
  resources:
    requests:
      storage: 10Gi
```

### Correct: Omit storageClassName to use cluster default or specify explicitly

```yaml
# GOOD -- uses the cluster's default StorageClass for dynamic provisioning
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 10Gi
---
# ALSO GOOD -- explicitly names the StorageClass
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc-explicit
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 10Gi
```

### Wrong: Using Immediate binding in multi-zone clusters

```yaml
# BAD -- PV may be provisioned in zone-a but Pod scheduled in zone-b
# This causes the Pod to be stuck in Pending with volume node affinity conflict
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: bad-multizone
provisioner: ebs.csi.aws.com
volumeBindingMode: Immediate
```

### Correct: Use WaitForFirstConsumer for multi-zone awareness

```yaml
# GOOD -- PV is provisioned in the same zone as the scheduled Pod
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: good-multizone
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
```

### Wrong: Expecting PVCs to be deleted when StatefulSet is deleted

```bash
# BAD -- deleting a StatefulSet does NOT delete its PVCs
# Orphaned PVCs accumulate storage cost silently
kubectl delete statefulset myapp
# PVCs data-myapp-0, data-myapp-1, data-myapp-2 still exist and are billed
```

### Correct: Explicitly clean up PVCs after StatefulSet deletion

```bash
# GOOD -- delete PVCs explicitly after confirming data is backed up
kubectl delete statefulset myapp
kubectl delete pvc -l app=myapp  # Only after verifying backups
```

### Wrong: Trying to re-bind a Released PV to a new PVC

```yaml
# BAD -- a PV in Released phase still has a claimRef to the old PVC
# New PVCs will NOT automatically bind to a Released PV
# The PV just sits there unusable
```

### Correct: Clear claimRef to make a Retained PV available again

```bash
# GOOD -- remove the old claimRef so the PV becomes Available
kubectl patch pv my-pv -p '{"spec":{"claimRef": null}}'
# Now a new PVC with matching criteria can bind to this PV
```

## Common Pitfalls

- **PVC stuck in Pending (no StorageClass)**: The PVC references a `storageClassName` that does not exist or no default StorageClass is set. Fix: `kubectl get sc` to list available classes, then set the correct name in the PVC spec. [src1]
- **PVC stuck in Pending (WaitForFirstConsumer)**: With `WaitForFirstConsumer` binding mode, the PVC stays `Pending` until a Pod that uses it is actually scheduled. This is expected behavior, not an error. Fix: Create a Pod/Deployment that references the PVC. [src2]
- **Volume node affinity conflict**: The PV was provisioned in a different zone than where the Pod is being scheduled. Fix: Use `volumeBindingMode: WaitForFirstConsumer` on the StorageClass to ensure zone-aware provisioning. [src7]
- **Cannot resize PVC (no allowVolumeExpansion)**: The StorageClass does not have `allowVolumeExpansion: true`. Fix: Create a new StorageClass with expansion enabled and migrate data, or ask the cluster admin to patch the existing StorageClass. [src4]
- **Filesystem resize not completing**: After a PV is expanded by the cloud provider, the filesystem resize requires the volume to be mounted by a Pod. Fix: Ensure a Pod using the PVC is running; check events with `kubectl describe pvc`. [src4]
- **Orphaned PVCs after StatefulSet scale-down**: Kubernetes does not delete PVCs when a StatefulSet is scaled down or deleted. Fix: Manually delete unwanted PVCs with `kubectl delete pvc <name>` after confirming backups. [src8]
- **Released PV not binding to new PVC**: A PV with `Retain` policy enters `Released` phase after its PVC is deleted. It retains the old `claimRef` and will not bind to new PVCs. Fix: `kubectl patch pv <name> -p '{"spec":{"claimRef": null}}'` to clear the reference. [src5]
- **Snapshot fails with no snapshot controller**: VolumeSnapshot CRDs are installed but the snapshot-controller Deployment is missing. Fix: Install the snapshot-controller from the `external-snapshotter` repository (it is separate from the CSI driver). [src3]

## Diagnostic Commands

```bash
# List all PVCs across namespaces with capacity and status
kubectl get pvc -A -o wide

# Describe a specific PVC to see events, conditions, and binding
kubectl describe pvc app-data -n default

# List all PVs with reclaim policy and status
kubectl get pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage,ACCESS:.spec.accessModes[0],RECLAIM:.spec.persistentVolumeReclaimPolicy,STATUS:.status.phase,CLAIM:.spec.claimRef.name,STORAGECLASS:.spec.storageClassName

# Check StorageClasses and their provisioners
kubectl get sc -o custom-columns=NAME:.metadata.name,PROVISIONER:.provisioner,RECLAIM:.reclaimPolicy,BINDING:.volumeBindingMode,EXPANSION:.allowVolumeExpansion

# Check which CSI drivers are installed
kubectl get csidrivers

# Find PVCs in Pending state and inspect why
kubectl get pvc -A --field-selector=status.phase=Pending

# Check volume expansion progress
kubectl describe pvc app-data | grep -A5 "Conditions"

# List VolumeSnapshots and their readiness
kubectl get volumesnapshot -A

# Check snapshot controller is running
kubectl get pods -n kube-system -l app=snapshot-controller

# Verify volume is mounted inside a Pod
kubectl exec app -- df -h /data

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

## Version History & Compatibility

| Version | Status | Key Changes | Migration Notes |
|---|---|---|---|
| Kubernetes 1.31+ | Current | PV deletion protection finalizers GA; VolumeAttributesClass beta | No migration needed from 1.29+ |
| Kubernetes 1.29 | Supported | ReadWriteOncePod (RWOP) GA; SELinuxMount alpha | RWOP requires CSI driver support |
| Kubernetes 1.27 | Supported | VolumeSnapshot GA; in-tree to CSI migration complete for major plugins | Migrate in-tree PVs to CSI: `kubectl get pv -o yaml` and update driver |
| Kubernetes 1.24 | EOL | Volume expansion GA; non-graceful node shutdown alpha | All expansion features stable from this version |
| Kubernetes 1.22 | EOL | Recycle reclaim policy deprecated; ephemeral volumes beta | Replace Recycle with Delete + dynamic provisioning |
| Kubernetes 1.13 | EOL | CSI 1.0 GA; dynamic provisioning GA | Upgrade from FlexVolume to CSI drivers |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Application needs data that survives Pod restarts and rescheduling | Data is temporary and can be regenerated | `emptyDir` volume |
| Running databases (PostgreSQL, MySQL, MongoDB) on Kubernetes | Using managed database services (RDS, Cloud SQL) | Cloud provider's managed DB |
| Need consistent snapshots and cloning for CI/CD test data | Backing up application-level data (SQL dumps preferred) | `pg_dump`, `mysqldump`, `mongodump` |
| Deploying a StatefulSet with per-replica storage | Running stateless services (APIs, web servers) | Deployment without PVCs |
| Multi-zone cluster needs zone-aware storage provisioning | Single-node development clusters (minikube, kind) | Default StorageClass with Immediate binding |
| Need shared read-write filesystem across Pods (RWX) | Block storage is sufficient for a single writer | RWO with block-backed StorageClass |

## Important Caveats

- **Access mode enforcement varies by driver**: The CSI driver, not Kubernetes, enforces access modes. Some drivers accept RWX in the PVC spec but silently provide RWO behavior. Always test concurrent access before relying on RWX in production. [src1]
- **Volume expansion is one-way only**: You can increase PVC size but **never shrink** it. To reduce capacity, create a new smaller PVC, copy data, and delete the old one. [src4]
- **In-tree volume plugins are deprecated**: As of Kubernetes 1.27, in-tree plugins (awsElasticBlockStore, gcePersistentDisk, azureDisk) are replaced by CSI drivers. Existing PVs using in-tree plugins continue to work via the CSI migration shim, but new deployments should use CSI. [src6]
- **StorageClass deletion does not affect existing PVCs**: Deleting a StorageClass does not delete PVs/PVCs created from it. However, new PVCs referencing the deleted class will fail. [src2]
- **WaitForFirstConsumer PVCs appear "stuck"**: This is normal behavior -- the PVC intentionally stays Pending until a Pod referencing it is scheduled. Do not interpret this as a failure. [src7]
- **Snapshot size may differ from PVC size**: Snapshots capture only written data blocks, not the full allocated capacity. The restored PVC must request at least the original PVC's size. [src3]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Kubernetes Deployment, Service & Ingress Basics](/software/devops/k8s-deployment-service-ingress/2026)
- [Kubernetes StatefulSet for Databases](/software/devops/k8s-statefulset-databases/2026)
- [Kubernetes Horizontal Pod Autoscaler](/software/devops/k8s-hpa/2026)
