Steven's Knowledge

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: 300

As 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.pem

Or 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 it

PVC + 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: data

Applying 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

ModeMeaningWhere you'll see it
ReadWriteOnce (RWO)One node at a timeMost cloud block storage (EBS, PD)
ReadOnlyMany (ROX)Many nodes, read-onlyShared assets
ReadWriteMany (RWX)Many nodes, read-writeNFS, EFS, FSx — slower
ReadWriteOncePod (RWOP)One pod at a timeNewer; 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

NeedUse
Non-secret configConfigMap
Secret configSecret (with External Secrets / Sealed Secrets in production)
Scratch space in a podemptyDir
Access node files (rare)hostPath
Persistent storage for one podPVC + StorageClass
Persistent storage per replicaStatefulSet + volumeClaimTemplates
Shared read-write across podsRWX PVC backed by NFS/EFS
TLS certkubernetes.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.

On this page