Chapter 21 of 47 · Configuration and resources

Immutable configuration and config templates

A ConfigMap you can edit at any time is handy, and dangerous. One quick edit changes production with no review, no history, and no easy way back. This chapter shows two habits that make config as safe as code: never edit config, release a new version of it, and, for big config files, keep one template and fill in only the parts that differ.

The problem with editing live config

Imagine changing the recipe card in a busy kitchen while cooks are using it. Some dishes follow the old card, some the new one, and nobody remembers what the old card said.

  1. $ kubectl edit configmap app-config      # LOG_LEVEL: info → debug, MAX_UPLOAD_MB: 10 → 1

    Someone edits the ConfigMap directly. Typo included: 1 instead of 100.

  2. posts-4kq2p   started yesterday   LOG_LEVEL=info   MAX_UPLOAD_MB=10
    posts-x7m2d   restarted today    LOG_LEVEL=debug  MAX_UPLOAD_MB=1

    Pods that were already running keep the old env values; any Pod that restarts gets the new ones (chapter 18). Now replicas behave differently, and only some uploads fail.

  3. $ kubectl rollout undo deployment/posts-depl
    deployment.apps/posts-depl rolled back      # …still broken

    Rolling back the Deployment does not help: it points at the same ConfigMap, which still holds the bad value. The old value is gone.

immutable: true

One line locks a ConfigMap or Secret. After it is created, its data can never be changed:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v1
immutable: true
data:
  LOG_LEVEL: info
  MAX_UPLOAD_MB: "10"
$ kubectl edit configmap app-config-v1
error: configmaps "app-config-v1" is invalid: data: Forbidden: field is immutable when `immutable` is set

Versioned names

Give each version its own name, and point the Deployment at the version it should use:

A Deployment pointing at versioned ConfigMapsConfigMaps app-config-v1 and app-config-v2 both exist and never change. The Deployment's Pod template names v2. Changing that name triggered a rolling update; switching back to v1 is a rollback. app-config-v1immutable · kept app-config-v2immutable · in use Deployment posts-deplenvFrom: app-config-v2 uses rollback target
  1. $ kubectl apply -f app-config-v2.yaml        # new name, new values, immutable

    Create the new version next to the old one. Nothing running changes yet.

  2. envFrom:
      - configMapRef:
          name: app-config-v2          # was app-config-v1
    $ kubectl apply -f posts-depl.yaml

    Change the name in the Deployment. That is a change to the Pod template, so Kubernetes does a normal rolling update (chapter 10). Readiness probes protect you as usual.

  3. $ kubectl rollout undo deployment/posts-depl

    Bad values? Roll back: the previous Pod template points at app-config-v1, which still exists, unchanged. A real, working rollback.

  4. $ kubectl delete configmap app-config-v0

    Clean up very old versions once you are sure no Deployment revision needs them.

Let a tool name the versions

Renaming by hand is easy to forget. Kustomize, built into kubectl, generates the ConfigMap and adds a short hash of its content to the name. Change any value and the name changes, so the Deployment rolls automatically:

# kustomization.yaml
resources:
  - posts-depl.yaml
configMapGenerator:
  - name: app-config
    literals:
      - LOG_LEVEL=info
      - MAX_UPLOAD_MB=10
$ kubectl apply -k .
configmap/app-config-5g2h7mt9kc created
deployment.apps/posts-depl configured

Kustomize also rewrites app-config in the Deployment to app-config-5g2h7mt9kc. You write the plain name; the tool handles versions.

Helm users get the same effect with a checksum/config annotation on the Pod template: a hash of the config file, so any config change alters the template and triggers a rollout.

Config inside an image

Another way to make config versioned and unchangeable: package the config files into their own small container image, such as posts-config:prod-2026-10. An init container copies them into a shared emptyDir (chapter 30) before the app starts.

initContainers:
  - name: config
    image: registry.example.com/posts-config:prod-2026-10
    command: ["cp", "-r", "/config/.", "/etc/app/"]
    volumeMounts: [{ name: app-config, mountPath: /etc/app }]
containers:
  - name: posts
    image: micro-posts:1.0
    volumeMounts: [{ name: app-config, mountPath: /etc/app }]
volumes:
  - name: app-config
    emptyDir: {}

When it is worth it: config bigger than a ConfigMap's 1 MiB limit, or when you want config to go through the same registry, scanning and promotion steps as your images. Otherwise immutable ConfigMaps are simpler.

Configuration templates

Sometimes a config file is long, but only a few lines differ between dev and production. Copying the whole file per environment means three files that slowly drift apart. Instead, keep one template with placeholders, and fill them in when the Pod starts:

# ConfigMap posts-proxy-template, key default.conf.template
server {
  listen 8080;
  client_max_body_size ${MAX_UPLOAD_MB}m;
  location / {
    proxy_pass http://${UPSTREAM_HOST}:4000;
  }
}

One file for every environment. Only ${MAX_UPLOAD_MB} and ${UPSTREAM_HOST} differ.

Which approach?

SituationUse
A few settings, small team, learningPlain ConfigMap; restart after changes (chapter 18)
Production, need history and rollbackImmutable, versioned ConfigMaps, ideally generated by Kustomize or Helm
Big config file, only a few values differ per environmentTemplate + env vars
Config over 1 MiB, or must follow image promotionConfig image + init container

Recap

Quick check

Why does kubectl rollout undo fix bad config with versioned ConfigMaps, but not with one edited ConfigMap?
Show answer

Rollback restores the previous Pod template. With versioned names, that template points at the old ConfigMap, which still has the old values. With one edited ConfigMap, the old template points at the same, already changed object.

Immutable configuration: common questions

What does immutable: true do on a ConfigMap?

It forbids any change to its data after creation and lets the kubelet stop watching it. To change values you create a new ConfigMap.