Skip to content

ConfigMaps in Depth

What You'll Learn

  • Every way a ConfigMap's data can reach a running container: env, envFrom, volume mount, and subPath
  • Why editing a ConfigMap updates a volume-mounted file automatically but never updates an already-injected environment variable
  • When to make a ConfigMap immutable, and what that trades away

Why This Matters

A ConfigMap is just a key-value store — the interesting decisions are all about how that data gets into a container, because each consumption path behaves differently when the ConfigMap changes later. Picking the wrong one is how teams end up debugging "why didn't my config change take effect" incidents that have nothing to do with the ConfigMap itself.

Mental Model

A ConfigMap holds data; it does nothing on its own. Every consumption pattern is really a question of when that data gets read: once, at container start (env vars), or continuously, for the life of the pod (volume mounts).

Pattern Reads ConfigMap Updates on ConfigMap change? Typical use
env (valueFrom.configMapKeyRef) Once, at container start No — requires pod restart A single, rarely-changing value
envFrom Once, at container start No — requires pod restart Bulk-importing many keys as env vars
Volume mount Continuously (kubelet syncs periodically) Yes — file content updates in place Config files an app can hot-reload, or that a sidecar watches
Volume mount with subPath Once, at container start No — this is the important exception Mounting a single key into an existing directory without shadowing other files

How It Works

env: a single key as one environment variable

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: myapp:2.3.1
      env:
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: log_level

envFrom: every key becomes an env var

    - name: app
      image: myapp:2.3.1
      envFrom:
        - configMapRef:
            name: app-config

Every key in app-config becomes an environment variable named after the key. This is convenient for a large flat config but has a real cost: there's no way to rename a variable in transit, keys with invalid environment-variable characters are silently skipped, and it's easy to accidentally shadow a variable set elsewhere (later envFrom/env entries win on conflict).

Volume mount: one file per key

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: myapp:2.3.1
      volumeMounts:
        - name: config
          mountPath: /etc/app/config
  volumes:
    - name: config
      configMap:
        name: app-config

Each key in app-config becomes a file at /etc/app/config/<key> inside the container. This is the pattern that supports live updates: kubelet periodically syncs the mounted ConfigMap's content (the default sync period is on the order of a minute, via the kubelet's configured cache TTL), and updates land as an atomic symlink swap — the application sees either the fully old or fully new version of every file, never a half-written mix.

subPath: the exception that breaks live updates

      volumeMounts:
        - name: config
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
  volumes:
    - name: config
      configMap:
        name: nginx-config

subPath mounts a single key as a file at an exact path, instead of mounting the whole ConfigMap as a directory. It's the right tool when you need to drop one file into a directory that already has other content (so you don't shadow it) — but Kubernetes implements subPath as a one-time bind mount, not the same symlink-swap mechanism as a directory mount. A subPath-mounted file will not update when the ConfigMap changes, full stop, regardless of how long you wait.

What this means for rollout triggers

Because env/envFrom values are frozen at container start and subPath mounts are frozen at pod creation, the only consumption pattern that updates live is a plain (non-subPath) volume mount — and even then, only if the application itself watches the file and reloads. Many applications don't; they read a config file once at startup and never look again, in which case a volume mount updates the file but the running process never notices.

The common, reliable pattern for "config changed, I need this rolled out" is to force a new pod generation rather than rely on live propagation:

# Trigger a rolling restart so every new pod picks up the current ConfigMap at startup
kubectl rollout restart deployment/app

A widely used trick to make this automatic: hash the ConfigMap's content into a pod template annotation, so any content change forces a new ReplicaSet without a human running the restart manually.

spec:
  template:
    metadata:
      annotations:
        checksum/config: "{{ configmap-content-sha256 }}"   # populated by Helm/Kustomize/CI, not literal Kubernetes syntax

Immutable ConfigMaps

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v3
data:
  log_level: "info"
immutable: true

Setting immutable: true prevents any further updates to data/binaryData — attempting to edit it is rejected by the API server. The trade-off is that "update the ConfigMap in place" is no longer possible; instead you create a new, versioned ConfigMap (app-config-v3, app-config-v4, …) and update the pod template to reference it, which naturally triggers a new rollout. The upside is real: kube-apiserver and kubelet can stop watching an immutable ConfigMap for changes entirely, which measurably reduces API server load on clusters with a very large number of ConfigMaps.

Common Mistakes

  • Editing a ConfigMap consumed via env/envFrom and expecting running pods to pick it up without a restart — they won't, ever, for already-running containers.
  • Using subPath and then being confused why a "working" live-reload setup stopped updating after switching from a directory mount.
  • Not versioning ConfigMap names for immutable configs, then hitting the "cannot update immutable field" API error mid-deployment.
  • Relying on kubelet's periodic sync as if it were instantaneous — there's a real (if usually short) propagation delay, so don't build a hard real-time dependency on it.

Interview Questions

  • Why does updating a volume-mounted ConfigMap take effect without a pod restart, but updating one consumed via env doesn't?
  • What specifically breaks about live-reload when a ConfigMap key is mounted with subPath?
  • What's the benefit of making a ConfigMap immutable, and what do you give up?

See Interview Prep for full answers.

Next

Continue to Secrets in Depth to see how the same consumption patterns apply — and don't apply — to sensitive data.