Skip to content

Storage — Example Manifests

emptydir-volume.yaml — Ephemeral emptyDir volume

# emptyDir Volume - Temporary storage
# Data is deleted when Pod is terminated
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-demo
  labels:
    app: storage-demo
spec:
  containers:
  - name: writer
    image: busybox:1.36
    command: ["/bin/sh", "-c"]
    args:
      - |
        while true; do
          echo "Writer: $(date)" >> /cache/data.txt
          echo "Written: $(tail -1 /cache/data.txt)"
          sleep 5
        done
    volumeMounts:
    - name: cache
      mountPath: /cache

  - name: reader
    image: busybox:1.36
    command: ["/bin/sh", "-c"]
    args:
      - |
        sleep 3
        while true; do
          echo "Reader: Last line from cache:"
          tail -1 /cache/data.txt
          sleep 5
        done
    volumeMounts:
    - name: cache
      mountPath: /cache

  volumes:
  - name: cache
    emptyDir: {}  # Empty directory on the node
    # Can also use:
    # emptyDir:
    #   medium: Memory  # Use tmpfs (RAM-backed)
    #   sizeLimit: 100Mi

persistent-volume.yaml — PersistentVolume and PVC

# PersistentVolume - Cluster-wide storage resource
# Note: In production, PVs are usually provisioned automatically by StorageClass
# This is a manual (static) provisioning example
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-hostpath
  labels:
    type: local
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce  # Can be mounted by one node at a time
  persistentVolumeReclaimPolicy: Retain  # Keep data when PVC is deleted
  hostPath:
    path: /mnt/data  # Path on the node (for testing only)
    type: DirectoryOrCreate
---
# PersistentVolumeClaim - Request for storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-hostpath
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi  # Request 500Mi, will bind to 1Gi PV
  # selector:  # Optional: select specific PV by labels
  #   matchLabels:
  #     type: local
---
# Pod using the PVC
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-pvc
  labels:
    app: storage-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["/bin/sh", "-c"]
    args:
      - |
        echo "Pod started at: $(date)" >> /data/log.txt
        echo "=== Current log file ==="
        cat /data/log.txt
        echo "========================"
        echo "Data persists across Pod restarts!"
        sleep 3600
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: pvc-hostpath

dynamic-provisioning.yaml — Dynamic storage provisioning

# Dynamic provisioning using StorageClass
# PersistentVolume is created automatically
# Note: Requires a StorageClass to be available (like local-path in kind)

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-dynamic
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: standard  # Use "local-path" in kind with local-path-provisioner
  resources:
    requests:
      storage: 2Gi
---
# Deployment using dynamic PVC
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-with-storage
  labels:
    app: storage-demo
spec:
  replicas: 1  # Only 1 replica because of ReadWriteOnce
  selector:
    matchLabels:
      app: storage-demo
  template:
    metadata:
      labels:
        app: storage-demo
    spec:
      containers:
      - name: app
        image: nginx:1.25-alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
        lifecycle:
          postStart:
            exec:
              command:
                - /bin/sh
                - -c
                - |
                  if [ ! -f /usr/share/nginx/html/index.html ]; then
                    echo "<h1>Hello from persistent storage!</h1>" > /usr/share/nginx/html/index.html
                    echo "<p>Pod: $HOSTNAME</p>" >> /usr/share/nginx/html/index.html
                    echo "<p>Started: $(date)</p>" >> /usr/share/nginx/html/index.html
                  fi
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: pvc-dynamic
---
# Service to access the app
apiVersion: v1
kind: Service
metadata:
  name: storage-demo-svc
spec:
  selector:
    app: storage-demo
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP

statefulset-storage.yaml — StatefulSet with storage

# StatefulSet with volumeClaimTemplates
# Each Pod gets its own PersistentVolumeClaim
# PVCs are named: <volumeClaimTemplate name>-<statefulset name>-<ordinal>

apiVersion: v1
kind: Service
metadata:
  name: postgres-headless
  labels:
    app: postgres
spec:
  clusterIP: None  # Headless service for StatefulSet
  selector:
    app: postgres
  ports:
  - port: 5432
    name: postgres
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres-headless
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16-alpine
        ports:
        - containerPort: 5432
          name: postgres
        env:
        - name: POSTGRES_PASSWORD
          value: mysecretpassword
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
  volumeClaimTemplates:  # Creates PVC for each Pod
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: standard  # Use "local-path" in kind
      resources:
        requests:
          storage: 5Gi

# This creates:
# Pod: postgres-0 with PVC: data-postgres-0
# Pod: postgres-1 with PVC: data-postgres-1
# Pod: postgres-2 with PVC: data-postgres-2
#
# Each Pod gets its own persistent storage that survives restarts
# PVCs are NOT deleted when StatefulSet is deleted (manual cleanup needed)

compose.yaml — Docker Compose comparison

# Docker Compose with volumes
services:
  # Application with persistent data
  app:
    image: nginx:1.25-alpine
    volumes:
      - app-data:/usr/share/nginx/html  # Named volume (persistent)
      - ./config:/etc/nginx/conf.d:ro   # Bind mount (local file)
    ports:
      - "8080:80"

  # Database with persistent storage
  database:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secretpassword
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - db-data:/var/lib/postgresql/data  # Named volume
    ports:
      - "5432:5432"

  # Temporary cache (no persistent storage needed)
  cache:
    image: redis:7.2-alpine
    # No volumes - data is ephemeral
    # Similar to emptyDir in Kubernetes

volumes:
  app-data:    # Docker manages these volumes
  db-data:

# Key Points:
# 1. Named volumes (app-data, db-data) = PersistentVolumeClaims in K8s
# 2. Volumes are automatically created and managed by Docker
# 3. Data persists across container restarts
# 4. Bind mounts (./config) = ConfigMaps or hostPath in K8s
# 5. No volume = ephemeral storage (like emptyDir)

# Kubernetes Equivalent:
# - app-data volume → PVC with 1Gi storage
# - db-data volume → PVC with 5Gi storage (or StatefulSet)
# - ./config bind mount → ConfigMap mounted as volume
# - cache (no volume) → emptyDir or no volume (ephemeral)

# Storage Management:
# Compose: docker volume ls, docker volume rm
# K8s: kubectl get pvc, kubectl delete pvc

# Backup/Restore:
# Compose: docker run --volumes-from <container> to backup
# K8s: VolumeSnapshots, backup tools, or manual copy