Today I learned one of the most confusing Kubernetes concepts for beginners: PersistentVolumes (PV) and PersistentVolumeClaims (PVC).
At first, my PVC refused to bind and stayed in the Pending state. After some troubleshooting, I finally got everything working. And honestly? Breaking it first helped me understand it much better.
Why do we even need PVs?
Imagine you're living in a rented apartment.
- Pod = You.
- Container filesystem = Your temporary room.
- PersistentVolume (PV) = A storage unit that exists independently of you.
- PersistentVolumeClaim (PVC) = The rental agreement saying, "I'd like to use a storage unit."
If your Pod dies, everything inside the container normally disappears.
A PV lets your data survive even when the Pod is deleted.
The Relationship
Pod
│
▼
PersistentVolumeClaim (PVC)
│
▼
PersistentVolume (PV)
│
▼
Actual storage (hostPath, NFS, cloud disk, etc.)
The Pod never talks directly to the PV.
Instead it says:
"Hey PVC, can I borrow some storage?"
The PVC then finds a suitable PV.
Step 1 — Create a PersistentVolume
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-demo
labels:
type: local
spec:
storageClassName: ""
capacity:
storage: 512Mi
accessModes:
- ReadWriteMany
hostPath:
path: "/data/config"
This creates 512Mi of storage backed by a directory on the node.
Step 2 — Create a PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-demo
spec:
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 256Mi
Notice that the claim only requests 256Mi.
Kubernetes looks for a PV that satisfies the request and binds them together.
My First Roadblock
After creating the PVC:
kubectl apply -f pvc.yaml
kubectl get pvc
I saw:
STATUS
Pending
Why Was My PVC Stuck in Pending?
This was the part that confused me the most.
I had already created my PersistentVolume (PV), so I expected my PersistentVolumeClaim (PVC) to bind to it immediately. Instead, every time I checked, the PVC was still in the Pending state.
kubectl get pvc
Output:
NAME STATUS
pvc-demo Pending
To investigate, I ran:
kubectl describe pvc pvc-demo
The Events section showed:
FailedBinding
no persistent volumes available for this claim and no storage class is set
At first, I thought Kubernetes couldn't see my PersistentVolume at all. That wasn't actually the problem.
The issue was that my PVC and PV didn't agree on the access mode.
My PVC requested:
accessModes:
- ReadWriteOnce
while my PV was configured with:
accessModes:
- ReadWriteMany
Because of this difference, Kubernetes couldn't match the claim to the volume, so the PVC stayed in the Pending state.
After deleting the old resources, correcting the configuration, and recreating both the PV and PVC, Kubernetes successfully matched them and their status changed to Bound.
The biggest lesson I learned is that whenever a PVC is stuck in Pending, the first command to run should be:
kubectl describe pvc pvc-demo
The Events section usually contains the clue you need to figure out why the claim couldn't be bound.
Step 3 — Mount the PVC inside a Pod
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: pv-container
image: nginx:latest
volumeMounts:
- name: pv-storage
mountPath: /var/app/config
ports:
- containerPort: 80
name: http-server
volumes:
- name: pv-storage
persistentVolumeClaim:
claimName: pvc-demo
Think of this as plugging an external hard drive into your laptop.
The Pod can now read and write files through the PVC.
Step 4 — Test It
kubectl exec -it app -- bash
Inside the container:
cd /var/app/config
echo "This is a test" > test.html
cat test.html
Output:
This is a test
Success!
I accidentally tried:
curl test.html
which failed because curl fetches URLs, not local files.
Lesson learned.
The Big Idea
Many beginners confuse PVs and PVCs.
Remember this:
- PersistentVolume (PV) = The actual storage.
- PersistentVolumeClaim (PVC) = The request for storage.
- Pod = The application that uses the claim.
A simple analogy:
- 🍕 PV = The pizza.
- 🧾 PVC = The order receipt.
- 😋 Pod = The hungry person.
You don't grab the pizza straight from the oven—you present your receipt first!
Commands I Used
kubectl apply -f pv.yaml
kubectl get pv
kubectl apply -f pvc.yaml
kubectl get pvc
kubectl describe pvc pvc-demo
kubectl apply -f pod.yaml
kubectl get pods
kubectl exec -it app -- bash
What I Learned
- Persistent storage survives Pod restarts.
- Pods use PVCs, not PVs, directly.
- A PVC must successfully bind before a Pod can use it.
kubectl describe is one of the best debugging tools when something is stuck.