Config & Storage
ConfigMaps, Secrets, Volumes, and PersistentVolumes - getting configuration and data to your pods
Config & Storage
Pods are ephemeral. Their containers should be stateless wherever possible. Two things they still need from the outside: configuration and persistent data.
ConfigMaps
Non-secret key/value config — feature flags, log levels, hostnames.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
config.yaml: |
server:
port: 3000
timeout: 30s
cache:
ttl: 300As Environment Variables
spec:
containers:
- name: app
image: myapp:v1
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef: { name: app-config, key: LOG_LEVEL }
# Or bring in all keys at once:
envFrom:
- configMapRef: { name: app-config }As Files
spec:
containers:
- name: app
image: myapp:v1
volumeMounts:
- name: config
mountPath: /etc/myapp
volumes:
- name: config
configMap:
name: app-config
items:
- { key: config.yaml, path: config.yaml }The container sees /etc/myapp/config.yaml. Updates to the ConfigMap propagate to the file within ~60s — but most apps don't notice unless they actively re-read. For env vars, restart pods after a config change.
Secrets
Same shape as ConfigMap, but for sensitive data — passwords, API tokens, TLS keys.
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData: # plaintext input; stored base64-encoded
url: "postgres://user:password@postgres:5432/mydb"
password: "secure-password"Mount the same way as a ConfigMap:
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef: { name: db-credentials, key: url }Kubernetes Secrets are base64-encoded, not encrypted at the API level. They're stored encrypted in etcd only if you've enabled encryption at rest. For real secret management:
- External Secrets Operator + your secret store (AWS Secrets Manager, Vault, GCP Secret Manager) — recommended
- Sealed Secrets — encrypted Secrets you can safely commit to git
- SOPS + git — encrypt before commit, decrypt on apply
TLS Secrets
A specific type for TLS, referenced by Ingress:
kubectl create secret tls api-tls \
--cert=path/to/fullchain.pem \
--key=path/to/privkey.pemOr get it managed automatically by cert-manager + Let's Encrypt.
Volumes
A Volume is storage mounted into a pod. The pod's containers can share it.
emptyDir — scratch space, lives with the pod
spec:
containers:
- name: app
image: myapp:v1
volumeMounts:
- { name: cache, mountPath: /var/cache }
volumes:
- name: cache
emptyDir: {}Gone when the pod dies. Good for caches, scratch files, IPC between containers in the same pod.
hostPath — file from the node (rare, careful)
volumes:
- name: docker-sock
hostPath: { path: /var/run/docker.sock }Couples the pod to the node. Reserve for monitoring/CNI/log shippers (DaemonSets).
PersistentVolumes (PV) & Claims (PVC)
Real persistent storage. Two-layer abstraction:
- PersistentVolume (PV) — a piece of storage on the cluster (cloud disk, NFS, ...).
- PersistentVolumeClaim (PVC) — a pod's request for storage of a certain size and access mode.
You almost never write PVs directly — a StorageClass plus a PVC creates the PV dynamically.
StorageClass (typically pre-installed by your cloud)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com # the cloud CSI driver
parameters:
type: gp3
iops: "3000"
throughput: "125"
reclaimPolicy: Delete # or Retain
volumeBindingMode: WaitForFirstConsumer # delay provisioning until a pod claims itPVC + pod
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"] # one node at a time
storageClassName: fast-ssd
resources:
requests: { storage: 20Gi }
---
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:v1
volumeMounts:
- { name: data, mountPath: /data }
volumes:
- name: data
persistentVolumeClaim:
claimName: dataApplying this provisions an EBS/PD/Disk, attaches it to the node where the pod runs, and mounts it at /data inside the container.
Access Modes
| Mode | Meaning | Where you'll see it |
|---|---|---|
ReadWriteOnce (RWO) | One node at a time | Most cloud block storage (EBS, PD) |
ReadOnlyMany (ROX) | Many nodes, read-only | Shared assets |
ReadWriteMany (RWX) | Many nodes, read-write | NFS, EFS, FSx — slower |
ReadWriteOncePod (RWOP) | One pod at a time | Newer; for strict single-writer |
StatefulSet + volumeClaimTemplates
Inside a StatefulSet, volumeClaimTemplates creates one PVC per replica automatically:
spec:
volumeClaimTemplates:
- metadata: { name: pgdata }
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests: { storage: 50Gi }postgres-0 gets pgdata-postgres-0, postgres-1 gets pgdata-postgres-1, ... and each PVC follows its pod across reschedules.
Quick Reference
| Need | Use |
|---|---|
| Non-secret config | ConfigMap |
| Secret config | Secret (with External Secrets / Sealed Secrets in production) |
| Scratch space in a pod | emptyDir |
| Access node files (rare) | hostPath |
| Persistent storage for one pod | PVC + StorageClass |
| Persistent storage per replica | StatefulSet + volumeClaimTemplates |
| Shared read-write across pods | RWX PVC backed by NFS/EFS |
| TLS cert | kubernetes.io/tls Secret (often via cert-manager) |
What's Next
Workloads, networking, and storage are wired up. Next, scale them — autoscaling, rollouts, and how to debug when things go wrong → Scaling & Rollouts.