Kubernetes Persistent Volume (PV) and Persistent Volume Claim (PVC): Practical Guide with Nginx and hostPath

Kubernetes applications often need persistent storage to keep data even when containers restart or are recreated. Kubernetes provides Persistent Volumes (PV) and Persistent Volume Claims (PVC) to manage this storage.

In this practical lab, we will create a PersistentVolume, create a PersistentVolumeClaim, attach the PVC to an Nginx Pod, and use a node’s /mnt/data directory as persistent storage through hostPath.


What You Will Learn

In this practical, we will understand:

  • What a Persistent Volume (PV) is
  • What a Persistent Volume Claim (PVC) is
  • How PV and PVC are connected
  • How to create a PV manually
  • How to create a PVC
  • How to attach a PVC to a Pod
  • How hostPath works
  • How Nginx uses persistent storage
  • How data written on the Kubernetes node becomes available inside the Pod

1. PV and PVC Architecture

The basic architecture of our lab is:

                Kubernetes Cluster
                       │
                       │
              ┌────────▼────────┐
              │       PV        │
              │      pv1         │
              │      1Gi         │
              │   hostPath      │
              │   /mnt/data     │
              └────────┬────────┘
                       │
                    Bound
                       │
              ┌────────▼────────┐
              │       PVC       │
              │      pvc1       │
              │      1Gi        │
              └────────┬────────┘
                       │
                       │ claimName: pvc1
                       ▼
              ┌─────────────────┐
              │       Pod       │
              │    webserver    │
              │                 │
              │     Nginx       │
              │                 │
              │ /usr/share/     │
              │ nginx/html      │
              └────────┬────────┘
                       │
                       ▼
                  index.html
                       │
                       ▼
              Hello nginx webserver

The important relationship is:

Pod → PVC → PV → Actual Storage


2. What is a Persistent Volume?

A PersistentVolume (PV) is a storage resource in a Kubernetes cluster.

Unlike a normal container filesystem, data stored on a PV can persist beyond the lifecycle of a container.

A PV is a cluster-level resource, meaning it does not belong to a particular namespace.

In our example, we create a PV named pv1 with:

  • Capacity: 1Gi
  • Access mode: ReadWriteOnce
  • Storage class: manual
  • Storage type: hostPath
  • Path: /mnt/data

3. Creating the Persistent Volume

First, check the available nodes:

kubectl get nodes

Create the PV configuration:

vim pv.yaml

Add the following configuration:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"

Apply the configuration:

kubectl apply -f pv.yaml

Check the PV:

kubectl get pv

You should see a PV named:

pv1

with approximately:

Capacity:       1Gi
Access Modes:   RWO
StorageClass:   manual

4. Understanding hostPath

The following section is particularly important:

hostPath:
  path: "/mnt/data"

hostPath tells Kubernetes to use a directory from the filesystem of the node where the Pod is running.

In our example:

Node01
   │
   └── /mnt/data
          │
          └── index.html

This directory will ultimately be available inside the Nginx container at:

/usr/share/nginx/html

So we have:

Node01
/mnt/data
    │
    │ hostPath
    ▼
PV pv1
    │
    ▼
PVC pvc1
    │
    ▼
Nginx Pod
    │
    ▼
/usr/share/nginx/html

5. Creating the Persistent Volume Claim

A PersistentVolumeClaim (PVC) is a request for storage.

The user or application doesn’t normally need to know all the details of the underlying storage. Instead, it requests the amount and type of storage it needs.

Create the PVC:

vim pvc.yaml

Add:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Apply it:

kubectl apply -f pvc.yaml

Check the PVC:

kubectl get pvc

Then check the PV:

kubectl get pv

The PVC should become:

STATUS: Bound

This means Kubernetes successfully matched the PVC with a suitable PV.

The relationship is:

PVC pvc1
    │
    │ Bound
    ▼
PV pv1

6. Understanding PV and PVC Binding

Our PV provides:

1Gi
ReadWriteOnce
StorageClass: manual

Our PVC requests:

1Gi
ReadWriteOnce
StorageClass: manual

Because the requirements match, Kubernetes can bind them.

        PV
       pv1
       1Gi
        ▲
        │
       Bound
        │
        ▼
       PVC
      pvc1
      1Gi

This is one of the most important concepts to understand when working with Kubernetes storage.


7. Creating the Nginx Pod

Now we need an application that will use the PVC.

Create:

vim pod1.yaml

Use:

apiVersion: v1
kind: Pod
metadata:
  name: webserver
spec:
  volumes:
    - name: vol1
      persistentVolumeClaim:
        claimName: pvc1

  containers:
    - name: nginx
      image: nginx
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: vol1

Apply the Pod:

kubectl apply -f pod1.yaml

Check:

kubectl get pods

You should see:

webserver

in the Running state.


8. Understanding the Pod Configuration

There are two important sections in the Pod configuration.

Volume

volumes:
  - name: vol1
    persistentVolumeClaim:
      claimName: pvc1

This tells Kubernetes:

Use the PVC named pvc1 as a volume for this Pod.

Then we mount that volume into the container:

volumeMounts:
  - mountPath: "/usr/share/nginx/html"
    name: vol1

This tells Kubernetes:

Mount vol1 inside the Nginx container at /usr/share/nginx/html.

Therefore:

PVC pvc1
    │
    ▼
Volume vol1
    │
    ▼
/usr/share/nginx/html

9. Checking the Nginx Storage

We can enter the Pod:

kubectl exec -it webserver -- bash

Then check:

ls -l /usr/share/nginx/html/

Initially, the directory may be empty because we haven’t created an index.html file in /mnt/data yet.

Exit the container:

exit

10. Checking the Node Storage

Now connect to the worker node:

ssh node01

Switch to root if required:

sudo -i

Check the directory:

ls -l /mnt/

Then:

ls -l /mnt/data/

Now create an HTML file:

echo "Hello nginx" > /mnt/data/index.html

Check the file:

ls -l /mnt/data/

You should now have:

/mnt/data/index.html

with the content:

Hello nginx

11. Accessing the File from the Nginx Pod

Return to the control plane:

exit

Then:

exit

Now enter the Pod again:

kubectl exec -it webserver -- bash

Check:

ls -l /usr/share/nginx/html/

You should now see:

index.html

The important thing is that we created the file on:

/mnt/data/index.html

but it is visible inside the container as:

/usr/share/nginx/html/index.html

This happens because of the PV/PVC and volume mount configuration.


12. Accessing Nginx Using curl

Now exit the Pod:

exit

From the control plane, run:

curl http://192.168.1.159

The response is:

Hello nginx

Why?

Nginx looks for:

/usr/share/nginx/html/index.html

Our mounted storage contains:

index.html

with:

Hello nginx

Therefore Nginx returns that content through HTTP.


13. Updating the Persistent Data

Now let’s demonstrate why the storage is useful.

Connect to node01 again:

ssh node01

Change the contents of the file:

echo "Hello nginx webserver" > /mnt/data/index.html

Now return to the control plane and run:

curl http://192.168.1.159

The result will be:

Hello nginx webserver

Notice that we didn’t modify the Nginx container.

We modified the file in:

/mnt/data/index.html

and Nginx immediately served the updated content.


14. Complete Data Flow

The complete flow of this practical is:

                 KUBERNETES CLUSTER
                         │
                         ▼
                 PersistentVolume
                      pv1
                       │
                 Capacity: 1Gi
                 Access: RWO
                 StorageClass: manual
                       │
                 hostPath /mnt/data
                       │
                       ▼
                 PersistentVolumeClaim
                      pvc1
                       │
                       │ Bound
                       ▼
                    Pod
                  webserver
                       │
                    nginx
                       │
                volumeMount
                       │
                       ▼
          /usr/share/nginx/html
                       │
                       ▼
                  index.html
                       │
                       ▼
              "Hello nginx webserver"
                       │
                       ▼
             curl 192.168.1.159

15. Static Provisioning

This practical demonstrates Static Provisioning.

Why?

Because we manually created the PV:

kubectl apply -f pv.yaml

before creating the PVC.

The process is:

Administrator
      │
      │ manually creates
      ▼
     PV
      │
      │ PVC requests storage
      ▼
     PVC
      │
      ▼
     Pod
      │
      ▼
   Application

Therefore:

Static Provisioning = Administrator manually creates the PersistentVolume.


16. PV vs PVC

FeaturePersistentVolume (PV)PersistentVolumeClaim (PVC)
PurposeProvides storageRequests storage
ScopeCluster-wideNamespace-specific
Created byUsually administrator/systemUser/application
Examplepv1pvc1
Storage1GiRequests 1Gi
Access ModeReadWriteOnceRequests ReadWriteOnce
Our storage/mnt/dataRequests PV storage

A simple way to remember:

PV = Storage
PVC = Request for Storage


17. Why Do We Use PVC?

A Pod generally should not have to know the details of the underlying storage.

For example, the application only needs:

"I need 1Gi of persistent storage."

The PVC represents that requirement.

Kubernetes then connects the PVC to an appropriate PV.

This creates a useful separation:

Application
     │
     ▼
    PVC
     │
     ▼
    PV
     │
     ▼
Storage

18. Important Note About hostPath

Our lab uses:

hostPath:
  path: "/mnt/data"

This is excellent for learning how PV, PVC, and Pod storage work.

However, hostPath has an important limitation.

The storage is associated with the local filesystem of a particular node.

For example:

Node01
  └── /mnt/data

and:

Node02
  └── /mnt/data

are normally two different directories.

Therefore, if a Pod moves from Node01 to Node02, it may not see the same data.

For production environments, Kubernetes commonly uses storage solutions based on CSI drivers and external storage systems, such as cloud disks, network filesystems, or distributed storage.


19. Commands Used in This Practical

Here is a consolidated list of the important commands:

kubectl get nodes

vim pv.yaml

kubectl apply -f pv.yaml

kubectl get pv

vim pvc.yaml

kubectl apply -f pvc.yaml

kubectl get pvc

kubectl get pv

vim pod1.yaml

kubectl apply -f pod1.yaml

kubectl get pods

kubectl exec -it webserver -- bash

ls -l /usr/share/nginx/html/

ssh node01

sudo -i

ls -l /mnt/

ls -l /mnt/data/

echo "Hello nginx" > /mnt/data/index.html

ls -l /mnt/data/

kubectl exec -it webserver -- bash

ls -l /usr/share/nginx/html/

curl http://192.168.1.159

ssh node01

echo "Hello nginx webserver" > /mnt/data/index.html

curl http://192.168.1.159

20. Final Result

We successfully created the following architecture:

        PV: pv1
          │
          │ 1Gi
          ▼
        PVC: pvc1
          │
          ▼
     Pod: webserver
          │
          ▼
    Container: nginx
          │
          ▼
/usr/share/nginx/html
          │
          ▼
      index.html
          ▲
          │
          │ hostPath
          │
     /mnt/data
       Node01

When we execute:

echo "Hello nginx webserver" > /mnt/data/index.html

the Nginx application can serve the same content:

curl http://192.168.1.159

Output:

Hello nginx webserver

Conclusion

Persistent Volumes and Persistent Volume Claims are fundamental concepts for managing storage in Kubernetes.

In this hands-on example, we manually created a 1Gi PersistentVolume (pv1) using hostPath, created a PersistentVolumeClaim (pvc1), and attached the PVC to an Nginx Pod (webserver). The Nginx container mounted the storage at /usr/share/nginx/html, while the underlying hostPath was /mnt/data on the worker node.

The most important concept to remember is:

Pod
 ↓
PVC
 ↓
PV
 ↓
Storage

And for this particular lab:

webserver
   ↓
pvc1
   ↓
pv1
   ↓
/mnt/data on Node01
   ↓
/usr/share/nginx/html in Nginx
   ↓
index.html

This practical demonstrates Static Provisioning with a hostPath-based PersistentVolume in Kubernetes.

Total Page Visits: 126 - Today Page Visits: 40

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *