Learning Kubernetes - Episode 21 - Introduction and Explanation of Volume

Learning Kubernetes - Episode 21 - Introduction and Explanation of Volume

In this episode, we'll discuss Kubernetes Volumes for persistent data storage. We'll learn about different volume types, how to mount volumes in Pods, and best practices for data persistence.

Arman Dwi Pangestu
Arman Dwi PangestuMarch 25, 2026
0 views
9 min read

Introduction

Note

If you want to read the previous episode, you can click the Episode 20 thumbnail below

Episode 20Episode 20

In the previous episode, we learned about Multi-Container Pods and design patterns for running multiple containers together. In episode 21, we'll discuss Volumes, the mechanism for persisting data in Kubernetes.

Note: Here I'll be using a Kubernetes Cluster installed through K3s.

By default, container filesystems are ephemeral - when a container restarts, all data is lost. Volumes solve this problem by providing persistent storage that survives container restarts and can be shared between containers in a Pod.

What Are Volumes?

A Volume is a directory accessible to containers in a Pod. Unlike the ephemeral container filesystem, volumes persist data beyond container restarts and can be shared between multiple containers.

Think of volumes like external hard drives - while your computer's internal storage is wiped when you reinstall the OS, an external drive keeps your data safe. Similarly, volumes preserve data when containers restart or crash.

Key characteristics of Volumes:

  • Persistent storage - Data survives container restarts
  • Shared access - Multiple containers can access the same volume
  • Multiple types - Different storage backends (local, cloud, network)
  • Pod-scoped - Volumes exist as long as the Pod exists
  • Flexible mounting - Mount at any path in container filesystem
  • Read/write modes - Support read-only and read-write access

Why Use Volumes?

Volumes solve several critical storage challenges:

  • Data persistence - Keep data when containers restart
  • Data sharing - Share files between containers in a Pod
  • Configuration injection - Mount ConfigMaps and Secrets as files
  • Log collection - Share log directories for sidecar containers
  • Database storage - Persist database data across restarts
  • Stateful applications - Run applications that need persistent state
  • Backup and recovery - Preserve data for disaster recovery

Without volumes, you would lose all data every time a container restarts, making it impossible to run stateful applications like databases.

Volume Lifecycle

Understanding volume lifecycle is crucial:

Pod-Scoped Volumes

Most volumes are tied to Pod lifecycle:

  1. Pod created - Volume is provisioned
  2. Container starts - Volume mounted into container
  3. Container restarts - Volume persists, data remains
  4. Pod deleted - Volume is destroyed (for ephemeral types)

Persistent Volumes

Some volumes persist beyond Pod lifecycle:

  1. Volume created - Independent of Pod
  2. Pod created - Volume attached to Pod
  3. Pod deleted - Volume remains available
  4. Volume deleted - Manual deletion required

Volume Types

Kubernetes supports many volume types for different use cases.

emptyDir

Temporary storage that exists as long as the Pod exists.

Use cases:

  • Scratch space for temporary data
  • Sharing data between containers
  • Cache storage
  • Checkpointing long computations

Example:

Kubernetesemptydir-volume.yml
apiVersion: v1
kind: Pod
metadata:
    name: emptydir-pod
spec:
    containers:
        - name: writer
          image: busybox:1.36
          command:
              - sh
              - -c
              - while true; do date >> /data/log.txt; sleep 5; done
          volumeMounts:
              - name: shared-data
                mountPath: /data
        - name: reader
          image: busybox:1.36
          command:
              - sh
              - -c
              - tail -f /data/log.txt
          volumeMounts:
              - name: shared-data
                mountPath: /data
    volumes:
        - name: shared-data
          emptyDir: {}

emptyDir with memory:

Kubernetesemptydir-memory.yml
volumes:
    - name: cache
      emptyDir:
          medium: Memory
          sizeLimit: 128Mi

This creates a tmpfs (RAM-backed filesystem) for high-performance temporary storage.

hostPath

Mounts a file or directory from the host node's filesystem.

Use cases:

  • Access Docker socket for container management
  • Access node logs
  • Development and testing
  • Node-level monitoring agents

Warning

Warning: hostPath volumes are not portable across nodes and pose security risks. Use only when necessary.

Example:

Kuberneteshostpath-volume.yml
apiVersion: v1
kind: Pod
metadata:
    name: hostpath-pod
spec:
    containers:
        - name: app
          image: nginx:1.25
          volumeMounts:
              - name: host-data
                mountPath: /usr/share/nginx/html
    volumes:
        - name: host-data
          hostPath:
              path: /data/web
              type: DirectoryOrCreate

hostPath types:

  • DirectoryOrCreate - Create directory if it doesn't exist
  • Directory - Must be existing directory
  • FileOrCreate - Create file if it doesn't exist
  • File - Must be existing file
  • Socket - Must be existing Unix socket
  • CharDevice - Must be existing character device
  • BlockDevice - Must be existing block device

configMap

Mounts ConfigMap data as files in the container.

Use cases:

  • Application configuration files
  • Environment-specific settings
  • Non-sensitive configuration data

Example:

Kubernetesconfigmap-volume.yml
apiVersion: v1
kind: ConfigMap
metadata:
    name: app-config
data:
    app.conf: |
        server {
            listen 80;
            server_name localhost;
        }
    database.conf: |
        host=db.example.com
        port=5432
---
apiVersion: v1
kind: Pod
metadata:
    name: configmap-pod
spec:
    containers:
        - name: app
          image: nginx:1.25
          volumeMounts:
              - name: config
                mountPath: /etc/config
    volumes:
        - name: config
          configMap:
              name: app-config

That configuration will create file app.conf and database.conf inside the container at path /etc/config. To see them, you can enter the container with the command sudo kubectl exec -it configmap-pod -- sh and view the files with the command cat /etc/config/app.conf and cat /etc/config/database.conf.

Mount specific keys:

Kubernetesconfigmap-specific-keys.yml
volumes:
    - name: config
      configMap:
          name: app-config
          items:
              - key: app.conf
                path: nginx.conf

secret

Mounts Secret data as files in the container.

Use cases:

  • Database passwords
  • API keys
  • TLS certificates
  • OAuth tokens

Example:

Kubernetessecret-volume.yml
apiVersion: v1
kind: Secret
metadata:
    name: db-credentials
type: Opaque
stringData:
    username: admin
    password: secretpassword
---
apiVersion: v1
kind: Pod
metadata:
    name: secret-pod
spec:
    containers:
        - name: app
          image: myapp:latest
          volumeMounts:
              - name: credentials
                mountPath: /etc/secrets
                readOnly: true
    volumes:
        - name: credentials
          secret:
              secretName: db-credentials

Files created:

  • /etc/secrets/username (contains "admin")
  • /etc/secrets/password (contains "secretpassword")

persistentVolumeClaim

References a PersistentVolumeClaim for durable storage.

Use cases:

  • Database storage
  • User uploads
  • Application state
  • Long-term data retention

Example:

Kubernetespvc-volume.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    name: data-pvc
spec:
    accessModes:
        - ReadWriteOnce
    resources:
        requests:
            storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
    name: pvc-pod
spec:
    containers:
        - name: app
          image: postgres:15
          volumeMounts:
              - name: data
                mountPath: /var/lib/postgresql/data
    volumes:
        - name: data
          persistentVolumeClaim:
              claimName: data-pvc

We'll cover PersistentVolumes and PersistentVolumeClaims in detail in the next episode.

downwardAPI

Exposes Pod metadata as files.

Use cases:

  • Pod name and namespace
  • Pod labels and annotations
  • Resource limits and requests

Example:

Kubernetesdownwardapi-volume.yml
apiVersion: v1
kind: Pod
metadata:
    name: downwardapi-pod
    labels:
        app: myapp
        version: v1.0
spec:
    containers:
        - name: app
          image: busybox:1.36
          command:
              - sh
              - -c
              - while true; do cat /etc/podinfo/*; sleep 10; done
          volumeMounts:
              - name: podinfo
                mountPath: /etc/podinfo
    volumes:
        - name: podinfo
          downwardAPI:
              items:
                  - path: name
                    fieldRef:
                        fieldPath: metadata.name
                  - path: namespace
                    fieldRef:
                        fieldPath: metadata.namespace
                  - path: labels
                    fieldRef:
                        fieldPath: metadata.labels

projected

Combines multiple volume sources into a single directory.

Use cases:

  • Mount multiple ConfigMaps together
  • Combine Secrets and ConfigMaps
  • Merge ServiceAccount tokens with config

Example:

Kubernetesprojected-volume.yml
apiVersion: v1
kind: Pod
metadata:
    name: projected-pod
spec:
    containers:
        - name: app
          image: nginx:1.25
          volumeMounts:
              - name: all-config
                mountPath: /etc/config
    volumes:
        - name: all-config
          projected:
              sources:
                  - configMap:
                        name: app-config
                  - secret:
                        name: db-credentials
                  - downwardAPI:
                        items:
                            - path: pod-name
                              fieldRef:
                                  fieldPath: metadata.name

Volume Mount Options

Customize how volumes are mounted in containers.

Read-Only Mounts

Prevent containers from modifying volume data:

Kubernetesreadonly-mount.yml
volumeMounts:
    - name: config
      mountPath: /etc/config
      readOnly: true

SubPath

Mount a specific file or subdirectory from a volume:

Kubernetessubpath-mount.yml
apiVersion: v1
kind: Pod
metadata:
    name: subpath-pod
spec:
    containers:
        - name: app
          image: nginx:1.25
          volumeMounts:
              - name: config
                mountPath: /etc/nginx/nginx.conf
                subPath: nginx.conf
    volumes:
        - name: config
          configMap:
              name: nginx-config

This mounts only the nginx.conf file, not the entire ConfigMap.

SubPathExpr

Use environment variables in subPath:

Kubernetessubpathexpr-mount.yml
volumeMounts:
    - name: data
      mountPath: /var/data
      subPathExpr: $(POD_NAME)
env:
    - name: POD_NAME
      valueFrom:
          fieldRef:
              fieldPath: metadata.name

Practical Examples

Example 1: Web Server with Persistent Content

Kubernetesweb-persistent.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    name: web-content-pvc
spec:
    accessModes:
        - ReadWriteOnce
    resources:
        requests:
            storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
    name: web-server
spec:
    replicas: 1
    selector:
        matchLabels:
            app: web
    template:
        metadata:
            labels:
                app: web
        spec:
            containers:
                - name: nginx
                  image: nginx:1.25
                  ports:
                      - containerPort: 80
                  volumeMounts:
                      - name: content
                        mountPath: /usr/share/nginx/html
            volumes:
                - name: content
                  persistentVolumeClaim:
                      claimName: web-content-pvc

Example 2: Application with Multiple Volume Types

Kubernetesmulti-volume-app.yml
apiVersion: v1
kind: Pod
metadata:
    name: multi-volume-app
spec:
    containers:
        - name: app
          image: myapp:latest
          volumeMounts:
              # Application config from ConfigMap
              - name: config
                mountPath: /etc/app/config
                readOnly: true
              # Database credentials from Secret
              - name: secrets
                mountPath: /etc/app/secrets
                readOnly: true
              # Persistent data storage
              - name: data
                mountPath: /var/lib/app
              # Temporary cache
              - name: cache
                mountPath: /tmp/cache
              # Logs shared with sidecar
              - name: logs
                mountPath: /var/log/app
        - name: log-shipper
          image: fluent/fluentd:v1.16
          volumeMounts:
              - name: logs
                mountPath: /var/log/app
                readOnly: true
    volumes:
        - name: config
          configMap:
              name: app-config
        - name: secrets
          secret:
              secretName: app-secrets
        - name: data
          persistentVolumeClaim:
              claimName: app-data-pvc
        - name: cache
          emptyDir:
              medium: Memory
              sizeLimit: 256Mi
        - name: logs
          emptyDir: {}

Example 3: Database with Persistent Storage

Kubernetesdatabase-persistent.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    name: postgres-pvc
spec:
    accessModes:
        - ReadWriteOnce
    resources:
        requests:
            storage: 20Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
    name: postgres
spec:
    serviceName: postgres
    replicas: 1
    selector:
        matchLabels:
            app: postgres
    template:
        metadata:
            labels:
                app: postgres
        spec:
            containers:
                - name: postgres
                  image: postgres:15
                  ports:
                      - containerPort: 5432
                  env:
                      - name: POSTGRES_PASSWORD
                        valueFrom:
                            secretKeyRef:
                                name: postgres-secret
                                key: password
                      - name: PGDATA
                        value: /var/lib/postgresql/data/pgdata
                  volumeMounts:
                      - name: data
                        mountPath: /var/lib/postgresql/data
            volumes:
                - name: data
                  persistentVolumeClaim:
                      claimName: postgres-pvc

Example 4: Init Container with Shared Volume

Kubernetesinit-shared-volume.yml
apiVersion: v1
kind: Pod
metadata:
    name: init-volume-pod
spec:
    initContainers:
        - name: setup
          image: busybox:1.36
          command:
              - sh
              - -c
              - |
                  echo "Downloading configuration..."
                  wget -O /config/app.conf https://config-server/app.conf
                  echo "Setup complete"
          volumeMounts:
              - name: config
                mountPath: /config
    containers:
        - name: app
          image: myapp:latest
          volumeMounts:
              - name: config
                mountPath: /etc/app
    volumes:
        - name: config
          emptyDir: {}

Volume Access Modes

Different volumes support different access modes:

ReadWriteOnce (RWO)

Volume can be mounted read-write by a single node:

Kubernetesyml
accessModes:
    - ReadWriteOnce

Most common for block storage (AWS EBS, GCE PD).

ReadOnlyMany (ROX)

Volume can be mounted read-only by many nodes:

Kubernetesyml
accessModes:
    - ReadOnlyMany

Useful for shared configuration or static content.

ReadWriteMany (RWX)

Volume can be mounted read-write by many nodes:

Kubernetesyml
accessModes:
    - ReadWriteMany

Requires network filesystem (NFS, CephFS, GlusterFS).

ReadWriteOncePod (RWOP)

Volume can be mounted read-write by a single Pod:

Kubernetesyml
accessModes:
    - ReadWriteOncePod

Kubernetes 1.22+ feature for strict single-Pod access.

Common Mistakes and Pitfalls

Mistake 1: Using emptyDir for Persistent Data

Problem: Data lost when Pod is deleted.

Solution: Use PersistentVolumeClaim for data that must survive Pod deletion:

Kubernetesyml
# Bad: emptyDir for database
volumes:
    - name: data
      emptyDir: {}
 
# Good: PVC for database
volumes:
    - name: data
      persistentVolumeClaim:
          claimName: db-pvc

Mistake 2: Forgetting readOnly for Secrets

Problem: Containers can modify sensitive data.

Solution: Always mount secrets as read-only:

Kubernetesyml
volumeMounts:
    - name: secrets
      mountPath: /etc/secrets
      readOnly: true

Mistake 3: Wrong Access Mode

Problem: Volume can't be mounted on multiple nodes.

Solution: Use ReadWriteMany for multi-node access:

Kubernetesyml
accessModes:
    - ReadWriteMany  # For shared access

Mistake 4: Not Setting Storage Limits

Problem: emptyDir can fill up node disk.

Solution: Set sizeLimit for emptyDir:

Kubernetesyml
emptyDir:
    sizeLimit: 1Gi

Mistake 5: Using hostPath in Production

Problem: Not portable, security risks, node dependency.

Solution: Use PersistentVolumes instead:

Kubernetesyml
# Avoid in production
hostPath:
    path: /data
 
# Use instead
persistentVolumeClaim:
    claimName: data-pvc

Best Practices

Use Appropriate Volume Types

Choose the right volume type for your use case:

Kubernetesyml
# Temporary data
emptyDir: {}
 
# Configuration
configMap:
    name: app-config
 
# Sensitive data
secret:
    secretName: credentials
 
# Persistent data
persistentVolumeClaim:
    claimName: data-pvc

Set Resource Limits

Always limit emptyDir size:

Kubernetesyml
emptyDir:
    sizeLimit: 500Mi

Use Read-Only Mounts

Mount volumes as read-only when possible:

Kubernetesyml
volumeMounts:
    - name: config
      mountPath: /etc/config
      readOnly: true

Organize Volume Mounts

Group related volumes logically:

Kubernetesyml
volumeMounts:
    # Configuration volumes
    - name: app-config
      mountPath: /etc/app
    - name: nginx-config
      mountPath: /etc/nginx
    # Data volumes
    - name: data
      mountPath: /var/lib/app
    # Temporary volumes
    - name: cache
      mountPath: /tmp/cache

Use SubPath Carefully

SubPath can cause issues with updates:

Kubernetesyml
# ConfigMap updates won't reflect with subPath
volumeMounts:
    - name: config
      mountPath: /etc/app/config.yml
      subPath: config.yml  # Blocks updates

Mount entire volume when possible.

Document Volume Purpose

Add comments explaining volume usage:

Kubernetesyml
volumes:
    # Application configuration files
    - name: config
      configMap:
          name: app-config
    # Database credentials
    - name: secrets
      secret:
          secretName: db-credentials
    # Persistent application data
    - name: data
      persistentVolumeClaim:
          claimName: app-data-pvc

Viewing Volume Information

Get Pod Volumes

Kubernetesbash
sudo kubectl get pod <pod-name> -o jsonpath='{.spec.volumes[*].name}'

Describe Pod

Kubernetesbash
sudo kubectl describe pod <pod-name>

Shows all volumes and their mounts.

Check Volume Mounts

Kubernetesbash
sudo kubectl exec <pod-name> -- df -h

View Volume Contents

Kubernetesbash
sudo kubectl exec <pod-name> -- ls -la /path/to/mount

Troubleshooting Volumes

Volume Not Mounting

Check Pod events:

Kubernetesbash
sudo kubectl describe pod <pod-name>

Look for mount errors in events.

Permission Denied

Check volume permissions and security context:

Kubernetesyml
securityContext:
    fsGroup: 1000
    runAsUser: 1000

ConfigMap/Secret Not Found

Verify resource exists:

Kubernetesbash
sudo kubectl get configmap <name>
sudo kubectl get secret <name>

Volume Full

Check disk usage:

Kubernetesbash
sudo kubectl exec <pod-name> -- df -h

Increase sizeLimit or clean up data.

Conclusion

In episode 21, we've explored Volumes in Kubernetes in depth. We've learned about different volume types, how to mount them in Pods, and best practices for data persistence.

Key takeaways:

  • Volumes provide persistent storage for containers
  • Data in volumes survives container restarts
  • emptyDir for temporary Pod-scoped storage
  • hostPath mounts node filesystem (use carefully)
  • configMap and secret for configuration injection
  • persistentVolumeClaim for durable storage
  • downwardAPI exposes Pod metadata as files
  • projected combines multiple volume sources
  • Volumes can be mounted read-only or read-write
  • subPath mounts specific files or directories
  • Choose appropriate access modes (RWO, ROX, RWX)
  • Always set sizeLimit for emptyDir volumes
  • Use PersistentVolumes for production data
  • Mount secrets as read-only for security

Volumes are essential for running stateful applications in Kubernetes. By understanding different volume types and their use cases, you can design robust storage solutions for your applications.

Are you getting a clearer understanding of Volumes in Kubernetes? Keep your learning momentum going and look forward to the next episode!

Note

If you want to continue to episode 21.1 where we deep-dive into PersistentVolume and PersistentVolumeClaim, you can click the Episode 21.1 thumbnail below

Episode 21.1Episode 21.1

Related Posts