Understanding PersistentVolumes (PV) and PersistentVolumeClaims (PVC)

Understanding PersistentVolumes (PV) and PersistentVolumeClaims (PVC)

1 3 15
calendar_today agoschedule3 min read

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.
Part 8 of 8 in My Kubernetes Journey
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

Understanding Kubernetes ClusterRoles and ClusterRoleBindings

AYANFE - Jul 7

Understanding Kubernetes RBAC: Roles, RoleBindings, and and Client Certificates

AYANFE - Jul 6

Understanding Kubernetes Service Accounts (The Identity Every Pod Uses)

AYANFE - Jul 7

Kubernetes Network Policies Explained (and How I Actually Made Them Work)

AYANFE - Jul 9

Have you ever left a tech meetup feeling like everyone else understood the conversation except you?

Ijay - Jul 2
chevron_left
360 Points19 Badges
Abuja,Nigeria.
10Posts
4Comments
7Connections
A gentleman with a rough edge.

Related Jobs

View all jobs →

Commenters (This Week)

1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!