Kubernetes Persistent Storage Solutions

Philip Rehberger Feb 28, 2026 6 min read

Manage stateful workloads in Kubernetes. Compare storage classes, CSI drivers, and backup strategies.

Kubernetes Persistent Storage Solutions

Kubernetes persistent storage enables stateful applications to store data that survives pod restarts and rescheduling. While containers are ephemeral by design, databases, file uploads, and session stores need durable storage. Understanding Kubernetes storage primitives helps you design reliable stateful workloads.

The storage abstraction layers—PersistentVolumes, PersistentVolumeClaims, and StorageClasses—separate storage provisioning from consumption. Developers request storage without knowing the underlying infrastructure. Administrators provision storage without knowing specific application needs. This separation enables portability and operational flexibility.

PersistentVolumes and Claims

PersistentVolumes (PV) represent storage resources in the cluster. They're provisioned by administrators or dynamically by StorageClasses. PersistentVolumeClaims (PVC) are requests for storage by users.

# Administrator-provisioned PersistentVolume
apiVersion: v1
kind: PersistentVolume
metadata:
  name: database-pv
spec:
  capacity:
    storage: 100Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: fast-ssd
  csi:
    driver: ebs.csi.aws.com
    volumeHandle: vol-0123456789abcdef0
---
# User's PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-storage
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 100Gi

Access modes define how volumes can be mounted:

  • ReadWriteOnce (RWO): Single node read-write
  • ReadOnlyMany (ROX): Multiple nodes read-only
  • ReadWriteMany (RWX): Multiple nodes read-write

Most block storage (EBS, Persistent Disk) supports only RWO. Shared filesystems (EFS, NFS) support RWX.

StorageClasses and Dynamic Provisioning

StorageClasses enable dynamic volume provisioning. When a PVC references a StorageClass, Kubernetes automatically provisions appropriate storage.

# StorageClass for fast SSD storage
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "10000"
  throughput: "500"
  encrypted: "true"
  kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd1234
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
# StorageClass for standard storage
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

WaitForFirstConsumer delays volume provisioning until a pod using the PVC is scheduled. This ensures the volume is created in the correct availability zone.

# PVC using dynamic provisioning
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 50Gi

StatefulSets for Stateful Applications

StatefulSets manage stateful applications with stable identities and persistent storage. Each pod gets a stable hostname and dedicated PVC.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:15
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: password
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 100Gi

Each replica gets its own PVC (data-postgres-0, data-postgres-1, data-postgres-2). PVCs persist even when pods are deleted, enabling data recovery.

Volume Snapshots

Volume snapshots provide point-in-time copies for backup and cloning.

# VolumeSnapshotClass
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: ebs-snapshot-class
driver: ebs.csi.aws.com
deletionPolicy: Retain
---
# Create snapshot
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: database-snapshot-20240115
spec:
  volumeSnapshotClassName: ebs-snapshot-class
  source:
    persistentVolumeClaimName: database-storage

Restore from snapshot by creating a new PVC:

# Restore from snapshot
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-storage-restored
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 100Gi
  dataSource:
    name: database-snapshot-20240115
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io

Backup Strategies

Combine volume snapshots with application-consistent backups for reliable recovery.

// Coordinate backup with application
class DatabaseBackupService
{
    public function createBackup(): BackupRecord
    {
        // 1. Flush and lock tables for consistent snapshot
        DB::statement('FLUSH TABLES WITH READ LOCK');

        try {
            // 2. Trigger volume snapshot
            $snapshot = $this->createVolumeSnapshot();

            // 3. Record backup metadata
            return BackupRecord::create([
                'snapshot_name' => $snapshot->name,
                'timestamp' => now(),
                'database_position' => $this->getBinlogPosition(),
            ]);
        } finally {
            // 4. Release lock
            DB::statement('UNLOCK TABLES');
        }
    }

    private function createVolumeSnapshot(): VolumeSnapshot
    {
        return $this->kubernetes->create([
            'apiVersion' => 'snapshot.storage.k8s.io/v1',
            'kind' => 'VolumeSnapshot',
            'metadata' => [
                'name' => 'database-backup-' . now()->format('Ymd-His'),
            ],
            'spec' => [
                'volumeSnapshotClassName' => 'ebs-snapshot-class',
                'source' => [
                    'persistentVolumeClaimName' => 'database-storage',
                ],
            ],
        ]);
    }
}

Shared Storage

Applications needing shared storage across multiple pods require RWX-capable storage like NFS or cloud-native shared filesystems.

# EFS StorageClass for shared storage (AWS)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: efs-shared
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap
  fileSystemId: fs-0123456789abcdef0
  directoryPerms: "755"
  gidRangeStart: "1000"
  gidRangeEnd: "2000"
---
# Shared PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-uploads
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-shared
  resources:
    requests:
      storage: 100Gi
---
# Deployment using shared storage
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: api
          image: myapp/api
          volumeMounts:
            - name: uploads
              mountPath: /var/uploads
      volumes:
        - name: uploads
          persistentVolumeClaim:
            claimName: shared-uploads

Local Persistent Volumes

Local volumes use directly-attached storage for performance-sensitive workloads. They're faster than network-attached storage but tied to specific nodes.

# StorageClass for local volumes
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
# PersistentVolume on specific node
apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv-node1
spec:
  capacity:
    storage: 500Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - node1.example.com

Local volumes don't survive node failures. Use them with applications that handle replication (databases with replicas, distributed storage systems).

Volume Expansion

Expand volumes without downtime when storage needs grow:

# Ensure StorageClass allows expansion
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: expandable
allowVolumeExpansion: true
# ... other config
# Expand PVC
kubectl patch pvc database-storage -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'

Some storage drivers require pod restart to complete expansion. Others expand online. Check driver documentation.

Monitoring Storage

Monitor storage capacity, performance, and health:

# Prometheus alerts for storage
groups:
  - name: storage
    rules:
      - alert: PVCAlmostFull
        expr: |
          kubelet_volume_stats_available_bytes / kubelet_volume_stats_capacity_bytes < 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "PVC {{ $labels.persistentvolumeclaim }} almost full"

      - alert: PVCExpansionFailed
        expr: kube_persistentvolumeclaim_status_condition{condition="Resizing"} == 1
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "PVC expansion stuck for {{ $labels.persistentvolumeclaim }}"

Conclusion

Kubernetes persistent storage enables stateful workloads through PersistentVolumes and PersistentVolumeClaims. StorageClasses automate provisioning with appropriate characteristics. StatefulSets manage stateful applications with stable identities and dedicated storage.

Choose storage types based on workload requirements: block storage for databases, shared filesystems for multi-pod access, local volumes for maximum performance. Implement backup strategies using volume snapshots. Monitor storage capacity to prevent outages from full volumes.

Share this article

Related Articles

Need help with your project?

Let's discuss how we can help you build reliable software.