2026年11月13日

2026年11月13日

Kubernetes PersistentVolume and PVC: Dynamic and Static Provisioning

This article is a localized edition of the Japanese original. Read the Japanese original

Files inside a Kubernetes Pod disappear when the Pod is deleted or recreated. Data you need to keep, such as database data or uploaded files, goes on a PersistentVolume (PV).

This article sorts out how PVs relate to PersistentVolumeClaims (PVCs) and explains two ways to attach storage to a Pod.

  • Dynamic provisioning: when you create a PVC, a PV is created automatically according to a StorageClass. This is the usual approach on managed Kubernetes in the cloud and on most clusters
  • Static provisioning: an administrator creates PVs by hand and PVCs bind to them

The manifests and output were checked as of October 2026 on Kubernetes 1.34 (k3s, whose default StorageClass is local-path).

How PV, PVC and StorageClass fit together

Resource Role Created by
PersistentVolume (PV) Represents actual storage (a disk, NFS, cloud block storage and so on). A cluster-wide resource An administrator, or automatically via a StorageClass
PersistentVolumeClaim (PVC) A request for "storage of this size with this access mode". A namespaced resource Whoever runs the app
StorageClass The kind of PV to create automatically (provisioner and parameters) An administrator

A Pod references a PVC by name, and the PV bound to that PVC is mounted. If you recreate the Pod and use the same PVC, you see the same data.

Check the StorageClass first

kubectl get storageclass
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  2s

If there is a StorageClass marked (default), creating a PVC is enough to get a PV created automatically (dynamic provisioning). The names and provisioners shown differ by cluster. If nothing is shown, prepare PVs yourself with static provisioning, or install a storage driver (CSI driver).

Use dynamic provisioning

Write a PVC and a Pod that mounts it in one file (web.yaml).

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
    - name: nginx
      image: nginx:1.28
      volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: web-data

Because storageClassName is not specified, the default StorageClass is used. To use a specific StorageClass, add storageClassName: <name> to the PVC's spec.

kubectl apply -f web.yaml
kubectl get pvc
NAME       STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
web-data   Bound    pvc-843d1db2-d309-40e5-b160-93506f1dc039   1Gi        RWO            local-path     <unset>                 18s

When STATUS becomes Bound, a PV has been created and bound to the PVC. If the StorageClass's VOLUMEBINDINGMODE is WaitForFirstConsumer, the PVC stays Pending until a Pod that uses it is created. That is normal.

Confirm that the data persists

Create a file inside the Pod, then delete and recreate the Pod and check that the file is still there.

kubectl exec web -- sh -c 'echo hello-pv > /usr/share/nginx/html/index.html'
kubectl delete pod web
kubectl apply -f web.yaml
kubectl exec web -- cat /usr/share/nginx/html/index.html

If the last command prints hello-pv, the data is stored on the PV.

In real deployments, it is more common to use PVCs from a Deployment (or from a StatefulSet's volumeClaimTemplates when each Pod needs its own volume) than to create Pods directly. The way you reference the PVC (persistentVolumeClaim under volumes) is the same.

Use static provisioning

This is how you create PVs yourself, for example to use an existing NFS server or disk. The following example is a hostPath PV that uses a directory on the node. hostPath is for testing on a single node: if the Pod runs on another node the data is not visible, so do not use it in production.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: manual-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /srv/k8s-data/manual-pv
    type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: manual-pvc
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
kubectl apply -f manual.yaml
kubectl get pv manual-pv
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
manual-pv   5Gi        RWO            Retain           Bound    default/manual-pvc   manual         <unset>                          3s

The key points are:

  • A PV needs exactly one volume type, such as hostPath, nfs, csi or local. A PV without one cannot be created and fails with spec: Required value: must specify a volume type
  • A PVC binds to a PV with the same storageClassName that satisfies its size and access mode requirements
  • If you omit storageClassName in the PVC, the default StorageClass is used, a new PV is created automatically, and the PVC does not bind to your hand-made PV. When using static PVs, write the same storageClassName in both the PV and the PVC (matching a name such as manual is enough, even without creating a StorageClass resource)

Using local volumes

To use a disk attached to a node in production, use a local volume instead of hostPath. A local PV must specify the node that has the disk with nodeAffinity, and the StorageClass should use WaitForFirstConsumer.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv-1
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/disks/vol1
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - worker-1

Replace worker-1 with a node name shown by kubectl get nodes, and /mnt/disks/vol1 with a directory actually mounted on that node. kubernetes.io/no-provisioner does not create PVs automatically, so prepare one PV per disk.

Access modes

Access mode Short form Meaning
ReadWriteOnce RWO Read-write from one node (multiple Pods on the same node can use it)
ReadOnlyMany ROX Read-only from many nodes
ReadWriteMany RWX Read-write from many nodes
ReadWriteOncePod RWOP Read-write by a single Pod only

Which access modes are available depends on the storage type. Cloud block storage, for example, usually supports only RWO; if Pods on multiple nodes need to read and write the same data (RWX), use a shared file system such as NFS.

Reclaim policy (what happens to the PV when the PVC is deleted)

The PV's persistentVolumeReclaimPolicy decides what happens to the PV and its data when the PVC is deleted.

Policy When the PVC is deleted
Retain The PV becomes Released and the data stays. An administrator cleans up by hand
Delete The PV and the underlying storage (such as a cloud disk) are deleted too

PVs created by dynamic provisioning inherit the StorageClass's reclaimPolicy (usually Delete). It is common for the setup to be such that deleting the PVC also deletes the data, so check before deleting a PVC that holds important data. Recycle is deprecated; do not use it.

A Retain PV becomes Released when its PVC is deleted, and as-is it does not bind to a new PVC. If you have checked the data and want to reuse the same PV, remove the reference to the old PVC (claimRef) and it returns to Available.

kubectl patch pv manual-pv --type json -p '[{"op":"remove","path":"/spec/claimRef"}]'

Increase capacity

If the StorageClass has allowVolumeExpansion set to true, you can grow a volume just by raising spec.resources.requests.storage on the PVC.

kubectl patch pvc web-data -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

You cannot shrink a volume. On a StorageClass where allowVolumeExpansion is false (or unset), this change is rejected.

When a PVC stays Pending

The reason is shown in Events at the end of kubectl describe pvc.

kubectl describe pvc PVC_NAME
Example in Events Cause and fix
waiting for first consumer to be created before binding The StorageClass uses WaitForFirstConsumer and no Pod uses the PVC yet. Creating the Pod moves it forward
storageclass.storage.k8s.io "manual" not found There is neither a PV nor a StorageClass matching the given storageClassName. Check that the PV's size, access mode and storageClassName match the PVC
no persistent volumes available for this claim and no storage class is set storageClassName is empty, there is no default StorageClass, and no PV meets the requirements
Waiting for a volume to be created either by the external provisioner ... keeps appearing The provisioner (CSI driver or similar) is not running or is failing. Check the logs of the provisioner's Pod

Check the list and state of PVs with kubectl get pv. Available means unused, Bound in use, Released the PVC has been deleted, and Failed automatic reclamation failed.

Summary

  • A Pod references a PVC, and the PV bound to that PVC is mounted. The data survives Pod recreation
  • With a default StorageClass, creating a PVC is enough to have a PV created automatically (dynamic provisioning)
  • When creating PVs by hand, specify the volume type (hostPath, nfs, local and so on) and use the same storageClassName on the PV and the PVC
  • Access modes depend on the storage type. Read-write from multiple nodes (RWX) requires a shared file system
  • Dynamically created PVs are often Delete, so deleting the PVC deletes the data. Protect important data with Retain and backups
  • If a PVC stays Pending, check the reason in Events of kubectl describe pvc

Related articles:

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲