Containers have ephemeral filesystems — when a container restarts, everything written inside is lost. Volumes solve this by providing storage that outlives individual containers. This lesson covers the primitive volume types that are built directly into the Pod spec — no external provisioning needed.

1. Volume Concepts

A volume has two parts in a Pod spec:

spec:
  volumes:                        # 1. DEFINE the volume (what type, what source)
    - name: my-volume
      emptyDir: {}
  containers:
    - name: app
      volumeMounts:               # 2. MOUNT it into a container (where)
        - name: my-volume
          mountPath: /data

Lifetime Rules

Volume TypeLifetimeSurvives Container Restart?Survives Pod Deletion?
emptyDirSame as Pod✅ Yes❌ No
hostPathSame as Node✅ Yes✅ Yes (data stays on node)
configMap / secretSame as Pod✅ Yes❌ No (but source object persists)
persistentVolumeClaimIndependent✅ Yes✅ Yes (PVC persists)

2. emptyDir — Scratch Space

Created when a Pod is assigned to a node. Starts empty. Deleted when the Pod is removed. Shared between all containers in the Pod.

spec:
  volumes:
    - name: scratch
      emptyDir: {}            # Default: node disk
    - name: cache
      emptyDir:
        medium: Memory        # tmpfs (RAM-backed) — faster, counts against memory limits
        sizeLimit: 500Mi      # Evict Pod if exceeded
  containers:
    - name: app
      volumeMounts:
        - name: scratch
          mountPath: /tmp/work
    - name: sidecar
      volumeMounts:
        - name: scratch
          mountPath: /shared    # Same volume, different mountPath — shared data!

Use Cases

Use CaseConfiguration
Sharing files between containers (sidecar pattern)emptyDir: {} mounted in both containers
Scratch space for sorting/processingemptyDir: {}
High-performance cacheemptyDir: {medium: Memory} (tmpfs)
Content from init containerInit container writes to emptyDir, app container reads
medium: Memory creates a tmpfs. It's fast (RAM speed), but: (1) counts against the container's memory limit, (2) data lost on Pod eviction, (3) limited by sizeLimit. If the Pod exceeds sizeLimit, it's evicted. Use it for caches and temporary data only.
The most common emptyDir pattern in production: an init container clones a config repo into an emptyDir, and the app container mounts the same emptyDir to read the config. This avoids baking config into the image while keeping the app container minimal.

3. hostPath — Node Filesystem Access

Mounts a file or directory from the host node's filesystem into the Pod. The data persists on that specific node even after the Pod is deleted.

spec:
  volumes:
    - name: host-logs
      hostPath:
        path: /var/log           # Path on the NODE
        type: Directory          # Must exist as a directory
  containers:
    - name: log-collector
      volumeMounts:
        - name: host-logs
          mountPath: /host-logs
          readOnly: true

hostPath Types

TypeBehavior
"" (empty)No checks — mount whatever exists (or create)
DirectoryOrCreateCreate directory if it doesn't exist (0755, kubelet user)
DirectoryMust already exist as a directory
FileOrCreateCreate file if it doesn't exist
FileMust already exist as a file
SocketMust be a Unix socket
CharDeviceMust be a character device
BlockDeviceMust be a block device
hostPath is a major security risk. It gives Pods direct access to the node filesystem — an attacker can read /etc/shadow, /var/lib/kubelet, or escape the container entirely by mounting /. In CKS context: Pod Security Standards (Restricted profile) prohibit hostPath. Only use it for system-level DaemonSets (logging, monitoring) with strict RBAC controls.

When hostPath Is Acceptable

  • DaemonSets for log collection — mount /var/log read-only
  • Node monitoring agents — mount /proc, /sys read-only
  • Container runtime socket — mount /var/run/containerd/containerd.sock
  • Static Pod manifests/etc/kubernetes/manifests
hostPath ties a Pod to a specific node. If the Pod is rescheduled to a different node, the data is gone (it's on the old node's disk). This makes hostPath unsuitable for persistent application data. Use PersistentVolumes for that.

4. Projected Volumes — Multiple Sources, One Mount

A projected volume combines multiple volume sources into a single directory. Instead of mounting 4 separate volumes, you mount one that contains files from all of them.

spec:
  volumes:
    - name: pod-info
      projected:
        sources:
          - configMap:
              name: app-config
              items:
                - key: app.properties
                  path: config/app.properties
          - secret:
              name: db-creds
              items:
                - key: password
                  path: secrets/db-password
          - downwardAPI:
              items:
                - path: metadata/labels
                  fieldRef:
                    fieldPath: metadata.labels
          - serviceAccountToken:
              path: token
              expirationSeconds: 3600
              audience: api-server
  containers:
    - name: app
      volumeMounts:
        - name: pod-info
          mountPath: /etc/pod-info
          readOnly: true
# Result in container:
/etc/pod-info/
├── config/app.properties      (from ConfigMap)
├── secrets/db-password        (from Secret)
├── metadata/labels            (from Downward API)
└── token                      (ServiceAccount token)

Sources You Can Project

SourceWhat It Provides
configMapConfigMap keys as files
secretSecret keys as files (tmpfs)
downwardAPIPod metadata (labels, annotations, resources)
serviceAccountTokenBound SA token (audience, expiry — replaces legacy tokens)
Projected ServiceAccount tokens are the modern approach. Instead of the legacy auto-mounted non-expiring token, projected tokens are short-lived (default 1h), audience-bound, and automatically rotated by kubelet. K8s 1.22+ uses these by default via BoundServiceAccountTokenVolume.
Projected volumes are used by the kubelet itself for mounting ServiceAccount tokens into Pods. When you see /var/run/secrets/kubernetes.io/serviceaccount/token inside a Pod, that's a projected volume with a bound SA token. Understanding this is key for CKS (token security) and debugging Pod authentication issues.

5. Generic Ephemeral Volumes (K8s 1.23+)

Like emptyDir but with PVC-backed storage: you get a dynamically provisioned volume that's automatically deleted when the Pod is removed. Useful when you need more storage features (specific StorageClass, size guarantees) but don't want to manage the PVC lifecycle separately.

spec:
  volumes:
    - name: scratch-pvc
      ephemeral:
        volumeClaimTemplate:
          metadata:
            labels:
              type: scratch
          spec:
            accessModes: ["ReadWriteOnce"]
            storageClassName: fast-ssd
            resources:
              requests:
                storage: 10Gi
  containers:
    - name: etl
      volumeMounts:
        - name: scratch-pvc
          mountPath: /work

Ephemeral vs emptyDir vs PVC

AspectemptyDirGeneric EphemeralPVC (Persistent)
Storage backendNode disk or tmpfsDynamic PV (CSI driver)Dynamic or static PV
Size guarantee❌ (sizeLimit is soft)✅ (actual provisioned volume)
Performance classWhatever the node hasConfigurable (StorageClass)Configurable
LifecycleDeleted with PodDeleted with Pod (auto)Independent (survives Pod)
Use caseSmall scratch space, sharing dataLarge temp storage (ETL, builds)Persistent data (databases)
Generic ephemeral volumes are ideal for CI/CD Pods (Jenkins agents, Tekton tasks) and data processing Jobs that need fast, large scratch space (SSD-backed) but don't need persistence. The volume is provisioned from your StorageClass and automatically cleaned up — no PVC garbage to manage.

6. Quick Reference: All Primitive Volume Types

TypeSourceMutable?Common Use
emptyDirNode disk or RAMScratch space, container sharing
hostPathNode filesystemDaemonSets (logs, /proc, sockets)
configMapConfigMap objectRead-only (auto-updates)Config files
secretSecret object (tmpfs)Read-only (auto-updates)Credentials, TLS certs
downwardAPIPod metadataRead-only (updates on label change)Pod identity info
projectedMultiple sources combinedRead-onlySA tokens + config + secrets
ephemeralDynamic PV (auto-delete)Large scratch, CI/CD, ETL
persistentVolumeClaimPVC → PVDatabases, stateful workloads

Summary

ConceptKey Point
VolumesDefined in spec.volumes, mounted in containers[].volumeMounts
emptyDirEmpty on creation, deleted with Pod, shared between containers
emptyDir Memorytmpfs (RAM-backed), counts against memory limits
hostPathNode filesystem — security risk, node-tied, use only for system DaemonSets
ProjectedCombines ConfigMap + Secret + DownwardAPI + SA token in one mount
EphemeralDynamic PVC created/deleted with Pod — large scratch with StorageClass
SA tokensProjected bound tokens (short-lived, audience-bound) are the modern default

📝 Quiz: Volume Primitives

Q1: An app container writes a file to /data. The container crashes and is restarted by kubelet. Is the file still there?

Depends on whether /data is a volume mount. If /data is backed by an emptyDir volume: yes, the file survives container restarts (emptyDir lives as long as the Pod). If it's just the container's writable layer (no volume): no, the file is lost on restart.

Q2: You set emptyDir: {medium: Memory, sizeLimit: 100Mi}. The app writes 150Mi. What happens?

The Pod is evicted. When a memory-backed emptyDir exceeds its sizeLimit, kubelet's eviction manager detects it and evicts the Pod. The sizeLimit is enforced by periodic checks (not instantaneous). Additionally, since it's tmpfs, the 150Mi usage counts against the container's memory limit — it could trigger an OOMKill before the eviction.

Q3: A Pod with a hostPath volume is rescheduled to a different node. What happens to the data?

The data stays on the original node and is NOT accessible from the new node. hostPath is node-local — there's no replication or migration. The Pod on the new node sees whatever is at that path on the new node (likely empty or different content). This is why hostPath is not suitable for persistent application data.

Q4: What's the security concern with hostPath: {path: /}?

Full node filesystem access. A container can read/write the entire host — including /etc/shadow (passwords), /var/lib/kubelet (secrets of all Pods), kubelet credentials, and SSH keys. This is essentially a container escape — the attacker has root on the node. Pod Security Standards (Restricted) prohibit this entirely.

Q5: What does a projected volume with serviceAccountToken provide that the legacy SA token mount doesn't?

Three improvements: (1) Time-bound — expires after expirationSeconds (default 1h), automatically rotated by kubelet. Legacy tokens never expire. (2) Audience-bound — token is valid only for a specific audience (API server, Vault, etc.). (3) Object-bound — invalidated if the Pod is deleted. Legacy tokens remain valid forever even after Pod deletion.

Q6: When would you use a generic ephemeral volume instead of emptyDir?

When you need: (1) Size guarantees — emptyDir's sizeLimit is a soft limit (Pod is evicted, not prevented from writing). Ephemeral volumes are real PVs with actual capacity. (2) Specific storage class — you need SSD-backed or high-IOPS storage, not whatever the node disk offers. (3) Large volumes — node disk might not have space, but a cloud block device can be dynamically provisioned.