Kubernetes Storage: PersistentVolumes, PVCs and StorageClasses (2026)

Kubernetes Storage Guide

1. Kubernetes Storage Concepts: Ephemeral vs Persistent

Kubernetes runs stateless workloads effortlessly, but real-world applications — databases, message brokers, file-processing pipelines — all need durable storage that survives pod restarts, rescheduling, and node failures. Understanding how Kubernetes models storage is the first step to building reliable stateful systems in 2026.

Ephemeral Storage

Ephemeral volumes exist only as long as the pod that owns them. Common types include:

  • emptyDir — a scratch directory created when a pod is assigned to a node; destroyed when the pod is removed.
  • configMap / secret — project configuration and credentials into the filesystem; backed by cluster state, not a real disk.
  • downwardAPI — expose pod metadata (labels, annotations) as files.
Key point: If a pod using an emptyDir volume is killed and rescheduled to a different node, all data in that volume is gone. Use persistent storage for anything you cannot afford to lose.

Persistent Storage and the PV/PVC/StorageClass Triangle

Kubernetes separates what storage exists from what storage is requested via a three-layer abstraction:

  • PersistentVolume (PV) — a piece of storage provisioned by an administrator or dynamically by a CSI driver. It is a cluster-level resource, independent of any pod or namespace.
  • PersistentVolumeClaim (PVC) — a namespaced request for storage made by a user or workload. Kubernetes binds the PVC to a suitable PV.
  • StorageClass — a template that describes how to provision storage (which provisioner to use, what disk type, replication factor, etc.). Dynamic provisioning creates a new PV automatically whenever a PVC referencing the StorageClass is created.

Think of it this way: a StorageClass is a recipe, a PVC is an order, and a PV is the dish delivered to the pod's table. For a broader overview of cluster architecture, see our Complete Kubernetes Guide.

2. PersistentVolume Spec: The Full YAML Reference

A PV describes a real storage resource. Here is a fully-annotated example using an AWS EBS volume provisioned statically (i.e., the disk already exists):

# pv-ebs-static.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-ebs-mysql
  labels:
    app: mysql
    environment: production
spec:
  # Total capacity of this volume
  capacity:
    storage: 50Gi

  # volumeMode: Filesystem (default) mounts as a directory.
  # Use Block for raw block device access (databases that manage their own I/O).
  volumeMode: Filesystem

  # accessModes define how the volume can be mounted:
  #   ReadWriteOnce (RWO)  — one node can mount read/write
  #   ReadOnlyMany  (ROX)  — many nodes can mount read-only
  #   ReadWriteMany (RWX)  — many nodes can mount read/write (requires NFS/EFS)
  #   ReadWriteOncePod     — only one pod (not just node) can mount R/W (K8s 1.22+)
  accessModes:
    - ReadWriteOnce

  # reclaimPolicy controls what happens to the PV when its PVC is deleted:
  #   Retain   — keep the volume and data; admin must manually reclaim
  #   Delete   — automatically delete the underlying storage asset
  #   Recycle  — deprecated; use dynamic provisioning instead
  persistentVolumeReclaimPolicy: Retain

  # storageClassName must match the PVC's storageClassName for binding
  storageClassName: gp3-retain

  # mountOptions are passed to the OS mount command
  mountOptions:
    - discard       # TRIM support for SSDs

  # nodeAffinity restricts which nodes can access this volume.
  # Required for zone-specific block devices like EBS.
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: topology.kubernetes.io/zone
              operator: In
              values:
                - ap-south-1a

  # Plugin-specific config. For EBS CSI driver use csi: block, not awsElasticBlockStore.
  csi:
    driver: ebs.csi.aws.com
    volumeHandle: vol-0a1b2c3d4e5f67890   # existing EBS volume ID
    fsType: ext4
Note: The legacy awsElasticBlockStore in-tree plugin is removed in Kubernetes 1.27+. Always use the EBS CSI driver (ebs.csi.aws.com) on modern clusters.

Access Mode Cheat Sheet

ModeShort CodeUse Case
ReadWriteOnceRWODatabases (MySQL, Postgres), single-writer apps
ReadOnlyManyROXShared config, static assets read by many pods
ReadWriteManyRWXShared uploads, NFS, EFS — multiple writers
ReadWriteOncePodRWOPExclusive single-pod access (K8s 1.22+, CSI only)

3. PersistentVolumeClaim: Requesting Storage

A PVC is a namespaced object that asks for a volume meeting specific criteria. Kubernetes' control plane watches for unbound PVCs and attempts to bind them to compatible PVs.

# pvc-mysql.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data-pvc
  namespace: production
  annotations:
    # Optional: document intended use
    app.techoral.com/purpose: "MySQL primary datadir"
spec:
  # Must match a PV's accessModes (or a superset)
  accessModes:
    - ReadWriteOnce

  # volumeMode must match the PV
  volumeMode: Filesystem

  resources:
    requests:
      # Kubernetes binds the smallest PV that satisfies this request
      storage: 20Gi

  # Referencing a StorageClass triggers dynamic provisioning.
  # Leave blank ("") to use the cluster default StorageClass.
  storageClassName: gp3-retain

  # Optional: selector pins the PVC to a specific PV using labels
  selector:
    matchLabels:
      app: mysql
      environment: production

Binding Lifecycle

A PVC moves through these phases:

  • Pending — no suitable PV found yet (or WaitForFirstConsumer mode is active).
  • Bound — a PV is matched and exclusively reserved for this PVC.
  • Lost — the bound PV has been deleted while the PVC still exists (data potentially gone).

Static vs Dynamic Provisioning

Static provisioning: An administrator pre-creates PVs manually. The PVC binds to one of them. Good for on-premises hardware or pre-existing cloud volumes.
Dynamic provisioning: A StorageClass and its provisioner (CSI driver) automatically create a new PV — and the underlying cloud disk — when a PVC is submitted. This is the standard approach in 2026 for cloud-native workloads.

Tip: Reference a PVC inside a pod via spec.volumes[].persistentVolumeClaim.claimName. The volume is then mounted at the path specified in spec.containers[].volumeMounts[].mountPath.
# pod using the PVC above
apiVersion: v1
kind: Pod
metadata:
  name: mysql-pod
  namespace: production
spec:
  containers:
    - name: mysql
      image: mysql:8.4
      env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: root-password
      volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
  volumes:
    - name: mysql-storage
      persistentVolumeClaim:
        claimName: mysql-data-pvc

4. StorageClass: The Provisioning Template

StorageClasses define the "type" of storage available in your cluster. Each cloud provider ships CSI drivers that read StorageClass parameters and translate them into API calls (create EBS volume, create EFS access point, etc.).

# storageclass-gp3.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
  annotations:
    # Mark as default — PVCs without storageClassName use this class
    storageclass.kubernetes.io/is-default-class: "true"

# The CSI driver responsible for provisioning
provisioner: ebs.csi.aws.com

# Parameters are driver-specific
parameters:
  type: gp3            # AWS EBS volume type (gp3 is the 2026 default)
  iops: "3000"         # Baseline IOPS (free tier for gp3)
  throughput: "125"    # MB/s throughput
  encrypted: "true"    # Encrypt at rest using default KMS key
  # kmsKeyId: "arn:aws:kms:..." # Use a custom KMS key (optional)

# What happens to the EBS volume when the PVC is deleted
reclaimPolicy: Delete

# Expand volumes online without downtime (requires EBS CSI 1.x+)
allowVolumeExpansion: true

# WaitForFirstConsumer defers PV creation until a pod is scheduled,
# ensuring the EBS volume is created in the correct AZ.
volumeBindingMode: WaitForFirstConsumer

# Restrict which topologies (AZs) this class can provision into
allowedTopologies:
  - matchLabelExpressions:
      - key: topology.kubernetes.io/zone
        values:
          - ap-south-1a
          - ap-south-1b
          - ap-south-1c
WaitForFirstConsumer vs Immediate: Always use WaitForFirstConsumer with zone-bound storage (EBS, Azure Disk). Immediate binding can create volumes in the wrong AZ, causing pods to be unschedulable.

5. Dynamic Provisioning End-to-End: StorageClass → PVC → Pod

The following example deploys a PostgreSQL database using fully dynamic storage. Apply the files in order:

# 1-storageclass.yaml — define the storage type
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: postgres-gp3
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  encrypted: "true"
reclaimPolicy: Retain        # Keep data even if PVC is deleted (safer for DBs)
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
# 2-pvc.yaml — request 100 GB
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: postgres-gp3
  resources:
    requests:
      storage: 100Gi
---
# 3-deployment.yaml — mount the PVC
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          env:
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: password
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: "2"
              memory: 2Gi
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: postgres-data
# Apply all three manifests
kubectl apply -f 1-storageclass.yaml
kubectl apply -f 2-pvc.yaml
kubectl apply -f 3-deployment.yaml

# Watch the PVC move from Pending → Bound once the pod is scheduled
kubectl get pvc postgres-data -w

# Verify the automatically created PV
kubectl get pv

# Confirm the pod is running and storage is mounted
kubectl describe pod -l app=postgres | grep -A5 Volumes

For guidance on structuring multi-replica deployments, see our Kubernetes Deployments guide.

6. AWS EBS CSI Driver: Installation and Configuration

The EBS CSI driver is the standard way to provision Amazon EBS volumes in EKS clusters. Starting with EKS 1.23, the in-tree EBS plugin is disabled and the CSI driver is mandatory.

Step 1 — Create IAM Policy and IRSA

# Download the official IAM policy
curl -o ebs-csi-iam-policy.json \
  https://raw.githubusercontent.com/kubernetes-sigs/aws-ebs-csi-driver/master/docs/example-iam-policy.json

# Create the policy in AWS
aws iam create-policy \
  --policy-name AmazonEKS_EBS_CSI_Driver_Policy \
  --policy-document file://ebs-csi-iam-policy.json

# Create an IAM role bound to the CSI driver ServiceAccount using IRSA
eksctl create iamserviceaccount \
  --name ebs-csi-controller-sa \
  --namespace kube-system \
  --cluster my-cluster \
  --attach-policy-arn arn:aws:iam::123456789012:policy/AmazonEKS_EBS_CSI_Driver_Policy \
  --approve \
  --role-only \
  --role-name AmazonEKS_EBS_CSI_DriverRole

Step 2 — Install via Helm

helm repo add aws-ebs-csi-driver \
  https://kubernetes-sigs.github.io/aws-ebs-csi-driver
helm repo update

helm upgrade --install aws-ebs-csi-driver \
  aws-ebs-csi-driver/aws-ebs-csi-driver \
  --namespace kube-system \
  --set controller.serviceAccount.create=false \
  --set controller.serviceAccount.name=ebs-csi-controller-sa \
  --set node.serviceAccount.create=false \
  --set node.serviceAccount.name=ebs-csi-controller-sa

# Verify pods are running
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver

Step 3 — Annotated ServiceAccount (IRSA)

# The eksctl command above creates this; shown for reference
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ebs-csi-controller-sa
  namespace: kube-system
  annotations:
    # This annotation links the K8s SA to the IAM Role via OIDC
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/AmazonEKS_EBS_CSI_DriverRole
EKS Managed Add-on: For EKS clusters, you can also enable the EBS CSI driver as a managed add-on: aws eks create-addon --cluster-name my-cluster --addon-name aws-ebs-csi-driver. This simplifies upgrades but gives less control over Helm values.

7. AWS EFS CSI Driver: ReadWriteMany Shared Storage

EBS volumes only support ReadWriteOnce — one node at a time. When multiple pods across different nodes need to share the same storage (e.g., a shared upload directory, ML model weights), use Amazon EFS with the EFS CSI driver which supports ReadWriteMany.

Install EFS CSI Driver

helm repo add aws-efs-csi-driver \
  https://kubernetes-sigs.github.io/aws-efs-csi-driver/
helm repo update

helm upgrade --install aws-efs-csi-driver \
  aws-efs-csi-driver/aws-efs-csi-driver \
  --namespace kube-system \
  --set controller.serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=\
arn:aws:iam::123456789012:role/AmazonEKS_EFS_CSI_DriverRole

StorageClass, PVC and Pod for EFS

# efs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap          # Use EFS Access Points for isolation
  fileSystemId: fs-0123456789abcdef # Your EFS file system ID
  directoryPerms: "700"
  gidRangeStart: "1000"
  gidRangeEnd: "2000"
  basePath: "/apps"
---
# efs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-uploads
  namespace: default
spec:
  accessModes:
    - ReadWriteMany    # Multiple pods on different nodes can read/write
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi     # EFS is elastic; this value is advisory only
---
# deployment using shared EFS volume (scale to many replicas freely)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: file-processor
spec:
  replicas: 3
  selector:
    matchLabels:
      app: file-processor
  template:
    metadata:
      labels:
        app: file-processor
    spec:
      containers:
        - name: app
          image: nginx:1.27
          volumeMounts:
            - name: uploads
              mountPath: /usr/share/nginx/html/uploads
      volumes:
        - name: uploads
          persistentVolumeClaim:
            claimName: shared-uploads
EFS vs EBS summary: Use EFS when you need ReadWriteMany or cross-AZ access. Use EBS when you need high-IOPS single-writer performance (databases). EFS costs more per GB but scales automatically with no pre-provisioning.

8. Volume Snapshots: Backup and Restore

Kubernetes 1.20+ provides a stable Volume Snapshot API (backed by the external-snapshotter controller) that works alongside CSI drivers to create point-in-time snapshots of PVCs.

# 1. VolumeSnapshotClass — defines which CSI driver handles snapshots
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: ebs-vsc
  annotations:
    snapshot.storage.kubernetes.io/is-default-class: "true"
driver: ebs.csi.aws.com
deletionPolicy: Delete   # Delete snapshot when VolumeSnapshot object is deleted
parameters:
  tagSpecification_1: "Name=k8s-snapshot-{{.VolumeSnapshotName}}"
---
# 2. VolumeSnapshot — take a snapshot of an existing PVC
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: postgres-data-snap-20260610
  namespace: default
spec:
  volumeSnapshotClassName: ebs-vsc
  source:
    persistentVolumeClaimName: postgres-data   # PVC to snapshot
# Watch snapshot until readyToUse: true
kubectl get volumesnapshot postgres-data-snap-20260610 -w

# List all snapshots
kubectl get volumesnapshot -A
# 3. Restore — create a new PVC from the snapshot
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data-restored
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: postgres-gp3
  resources:
    requests:
      storage: 100Gi
  # dataSource points to the snapshot instead of an empty volume
  dataSource:
    name: postgres-data-snap-20260610
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
Cross-namespace restore: Use dataSourceRef with a VolumeSnapshotContent reference if you need to restore a snapshot into a different namespace. This requires the cross-namespace data source feature gate (beta in K8s 1.26+).

9. StatefulSet with volumeClaimTemplates

StatefulSets are the standard way to run stateful applications like databases and message brokers in Kubernetes. Each pod in a StatefulSet gets its own dedicated PVC created automatically from a volumeClaimTemplates spec — no manual PVC creation needed.

# statefulset-cassandra.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cassandra
  namespace: default
spec:
  serviceName: cassandra          # Headless service name (required)
  replicas: 3
  selector:
    matchLabels:
      app: cassandra
  template:
    metadata:
      labels:
        app: cassandra
    spec:
      terminationGracePeriodSeconds: 180
      containers:
        - name: cassandra
          image: cassandra:5.0
          ports:
            - containerPort: 9042
              name: cql
            - containerPort: 7000
              name: intra-node
          env:
            - name: MAX_HEAP_SIZE
              value: "512M"
            - name: HEAP_NEWSIZE
              value: "100M"
            - name: CASSANDRA_SEEDS
              value: "cassandra-0.cassandra.default.svc.cluster.local"
          volumeMounts:
            - name: cassandra-data
              mountPath: /var/lib/cassandra/data
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: "2"
              memory: 2Gi

  # volumeClaimTemplates create one PVC per pod replica:
  #   cassandra-data-cassandra-0
  #   cassandra-data-cassandra-1
  #   cassandra-data-cassandra-2
  volumeClaimTemplates:
    - metadata:
        name: cassandra-data
      spec:
        accessModes:
          - ReadWriteOnce
        storageClassName: gp3
        resources:
          requests:
            storage: 200Gi
---
# Headless service required by StatefulSet for stable DNS
apiVersion: v1
kind: Service
metadata:
  name: cassandra
  namespace: default
spec:
  clusterIP: None       # Headless — no load balancing, returns pod IPs directly
  selector:
    app: cassandra
  ports:
    - port: 9042
      name: cql

When a StatefulSet pod is rescheduled (e.g., after a node failure), it reattaches to its original PVC by name, preserving identity and data. Deleting the StatefulSet does NOT automatically delete PVCs — data is safe until you explicitly kubectl delete pvc.

Scaling down: When you reduce StatefulSet replicas, the highest-ordinal PVCs are orphaned but not deleted. Scale back up to re-attach them. This is intentional — Kubernetes protects your data by default.

10. Troubleshooting Storage Issues

Storage problems are among the most common Kubernetes issues. Here is a structured approach to diagnosing and resolving them.

PVC Stuck in Pending

# Step 1: Describe the PVC to get the failure reason
kubectl describe pvc <pvc-name> -n <namespace>

# Look for Events like:
#   "no persistent volumes available for this claim and no storage class is set"
#   "waiting for first consumer to be created before binding"
#   "ProvisioningFailed: InvalidParameterValue: ..."

# Step 2: Check if a StorageClass exists
kubectl get storageclass

# Step 3: Check CSI driver pods are healthy
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver
kubectl logs -n kube-system -l app=ebs-csi-controller -c csi-provisioner --tail=50

Wrong Access Mode

# Error: "Multi-Attach error for volume — volume is already exclusively attached to one node"
# Cause: EBS (RWO) volume mounted by a second pod on a different node.
# Fix: Use a Deployment with 1 replica, or switch to EFS (RWX).

# Check what access modes the bound PV actually supports
kubectl get pv <pv-name> -o jsonpath='{.spec.accessModes}'

Node Affinity / Topology Conflicts

# Error pod stuck Pending: "node(s) had volume node affinity conflict"
# Cause: EBS volume was created in us-east-1a but pod is scheduled on us-east-1b node.

# Check PV node affinity
kubectl get pv <pv-name> -o yaml | grep -A10 nodeAffinity

# Check which AZ the node is in
kubectl get node <node-name> --show-labels | grep topology

# Fix: Set volumeBindingMode: WaitForFirstConsumer on your StorageClass
# so volumes are always created in the AZ where the pod lands.

Volume Expansion Not Working

# Edit the PVC to request more storage (StorageClass must have allowVolumeExpansion: true)
kubectl patch pvc postgres-data -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'

# Monitor expansion
kubectl describe pvc postgres-data | grep -E "Capacity|Conditions"

# For filesystem resize to complete, the pod may need to be restarted once
kubectl rollout restart deployment/postgres
Common gotcha: You can only increase PVC size, never decrease it. To shrink a volume, you must create a new PVC, copy data, and update your workload to use the new claim.

For more troubleshooting patterns related to resource limits and pod scheduling, see our Kubernetes Resource Management guide.

11. Storage Capacity and Resource Quotas

In multi-tenant clusters, administrators use ResourceQuota objects to cap how much storage a namespace can consume — both the number of PVCs and the total bytes requested.

# resourcequota-storage.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: storage-quota
  namespace: team-alpha
spec:
  hard:
    # Maximum number of PVCs in this namespace
    persistentvolumeclaims: "10"

    # Maximum total storage requested across all PVCs
    requests.storage: "500Gi"

    # Quotas scoped to a specific StorageClass:
    #   <storageClassName>.storageclass.storage.k8s.io/requests.storage
    gp3.storageclass.storage.k8s.io/requests.storage: "400Gi"
    gp3.storageclass.storage.k8s.io/persistentvolumeclaims: "8"

    # Prevent use of a specific class entirely (set to "0")
    # efs-sc.storageclass.storage.k8s.io/persistentvolumeclaims: "0"
# Apply the quota
kubectl apply -f resourcequota-storage.yaml

# Check current consumption vs quota
kubectl describe resourcequota storage-quota -n team-alpha

# Sample output:
# Name:                                                  storage-quota
# Namespace:                                             team-alpha
# Resource                                               Used   Hard
# --------                                               ----   ----
# gp3.storageclass.storage.k8s.io/persistentvolumeclaims 3      8
# gp3.storageclass.storage.k8s.io/requests.storage        150Gi  400Gi
# persistentvolumeclaims                                  3      10
# requests.storage                                        150Gi  500Gi

Storage Capacity Tracking (K8s 1.24+ GA)

Kubernetes 1.24 graduated Storage Capacity Tracking to GA. When enabled, the scheduler uses CSIStorageCapacity objects — published by CSI drivers — to make smarter placement decisions and avoid scheduling pods onto nodes where the volume cannot be provisioned due to capacity constraints.

# View storage capacity information published by the EBS CSI driver
kubectl get csistoragecapacity -A

# Example output shows capacity per topology zone:
# NAMESPACE     NAME                           STORAGECLASS   CAPACITY
# kube-system   ...ap-south-1a                 gp3            12Ti
# kube-system   ...ap-south-1b                 gp3            12Ti
Best practice: Always set namespace-level storage quotas in production clusters. Without them, a misconfigured deployment loop can provision thousands of volumes and generate significant cloud storage costs before anyone notices.

Summary: Storage Decision Flowchart

  • Temporary scratch space during job execution?emptyDir
  • Config / secrets as files?configMap / secret volume
  • Single-writer database (MySQL, Postgres, MongoDB)? → EBS gp3 PVC (RWO)
  • Shared read/write across multiple pods/nodes? → EFS PVC (RWX)
  • Multiple replicas each need their own volume? → StatefulSet + volumeClaimTemplates
  • Backup and restore? → VolumeSnapshot + restore PVC from snapshot
  • High-throughput block I/O (Cassandra, etcd)? → EBS io2 PVC with provisioned IOPS

To learn how storage interacts with security policies (e.g., read-only root filesystems, fsGroup), see our Kubernetes Security Best Practices guide. For configuring application settings that complement persistent data, see ConfigMaps and Secrets.

Quick Reference

Check PVC status:

kubectl get pvc -A
kubectl describe pvc <name>

List StorageClasses:

kubectl get storageclass

List PVs:

kubectl get pv

List snapshots:

kubectl get volumesnapshot -A
Stay Updated with Techoral

Get the latest Kubernetes, Cloud, and DevOps articles delivered to your inbox.