Containers are ephemeral: when a pod is rescheduled, anything written inside it is gone. Databases, uploads and other state need persistent storage. Kubernetes handles this with PersistentVolumes (PV), PersistentVolumeClaims (PVC) and StorageClasses. Understanding them, and their limits on self-managed VPS clusters, is key to running stateful apps safely.
The three objects
| Object | Who creates it | What it is |
|---|---|---|
| StorageClass | Admin (or the distribution) | A type of storage, and how to provision it (driver, parameters) |
| PersistentVolumeClaim (PVC) | The app (you) | “I need 10 Gi of ReadWriteOnce storage of class X” |
| PersistentVolume (PV) | Created dynamically by the provisioner (or manually) | The actual volume bound to the claim |
With dynamic provisioning, you only write the PVC. The StorageClass’s provisioner creates the PV.
kubectl get storageclass
kubectl get pvc,pv -A
A PVC and a pod using it
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: local-path
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 1
selector: { matchLabels: { app: app } }
template:
metadata: { labels: { app: app } }
spec:
containers:
- name: app
image: myapp:1.8.3
volumeMounts:
- name: data
mountPath: /var/lib/app
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
Access modes
| Mode | Meaning |
|---|---|
| ReadWriteOnce (RWO) | Mounted read-write by one node (most block storage) |
| ReadWriteOncePod | Only one pod |
| ReadOnlyMany (ROX) | Read-only by many nodes |
| ReadWriteMany (RWX) | Read-write by many nodes (needs NFS, CephFS, or similar) |
Most databases want RWO. Shared uploads across replicas need RWX, or better, object storage. See MinIO.
Storage options on VPS clusters
Without a cloud provider’s volume driver, you choose a provisioner:
| Option | How it works | Trade-off |
|---|---|---|
| local-path (k3s default) | Directory on the node’s disk | Simple and fast, but data is tied to one node |
| Longhorn | Replicates volumes across nodes’ disks | Survives node loss; uses extra disk and network |
| OpenEBS, Rook-Ceph | Distributed storage | Powerful; Ceph is complex and resource-hungry |
| NFS provisioner | Volumes on an NFS server | RWX support; NFS server is a single point of failure |
For a single-node k3s cluster on one VPS, local-path is fine. For multi-node clusters where pods may move, use Longhorn, or keep stateful services outside the cluster. See k3s and NFS server and client.
StatefulSets for databases
Databases and other stateful apps usually run as StatefulSets, which give each replica a stable name and its own PVC (via volumeClaimTemplates). Many teams run databases outside Kubernetes on a dedicated VPS, or use operators (CloudNativePG, Percona operators), which handle replication and backups.
Reclaim policy: don’t lose data by accident
kubectl get pv -o custom-columns=NAME:.metadata.name,RECLAIM:.spec.persistentVolumeReclaimPolicy
Delete(common default): deleting the PVC deletes the volume and its data.Retain: the volume is kept for manual recovery.
Set reclaimPolicy: Retain on StorageClasses for important data, or patch existing PVs:
kubectl patch pv <pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
Backups
Persistent volumes are not backups. Options:
- Velero: backs up Kubernetes resources and volume data (with restic/kopia) to S3-compatible storage.
- Application-level dumps (pg_dump, mysqldump) in a CronJob, pushed off-site.
- Longhorn backups to S3.
See backup strategy (3-2-1) and restic.
Expanding a volume
If the StorageClass has allowVolumeExpansion: true, edit the PVC’s storage request to a larger value. Many drivers resize online.
Quick reference
| Task | Command / field |
|---|---|
| List classes | kubectl get storageclass |
| Request storage | PVC with accessModes, storageClassName, storage |
| Status | kubectl get pvc,pv |
| Keep data on PVC delete | reclaimPolicy: Retain |
| Databases | StatefulSet / operator, or outside the cluster |
Related: Kubernetes deployments and services and Kubernetes debugging pods.