</>CodeWithKarani

Kubernetes Won't Restart Pods When a ConfigMap Changes: The checksum/config Pattern

Karani GeoffreyKarani Geoffrey6 min read

You updated a ConfigMap. You changed a feature flag, or a log level, or an API endpoint. You applied it, kubectl get configmap shows the new value, and your application is still running the old one. You wait. You wait some more. Nothing happens, because nothing is going to happen.

Then you find the advice thread, hundreds of votes deep, and everyone has a different workaround: delete the pods, scale to zero and back, add a random annotation, install a controller. It feels like the platform is missing an obvious feature. It is not. Kubernetes is doing exactly what it was designed to do, which is to never confuse "I changed some config" with "please roll out my application". Once you see why that separation exists, the fix stops feeling like a hack.

Kubernetes does not restart pods on ConfigMap or Secret changes, by design. Env-var config never updates without a pod restart; volume-mounted config eventually syncs the file but the app must re-read it. To force a rollout on change, put a hash of the config in the pod template so the template itself changes:

spec:
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}

Or install a controller like Stakater Reloader to do it automatically. As a one-off, kubectl rollout restart deployment/my-app forces a clean restart now.

The two kinds of config, and why only one ever "updates"

How a ConfigMap or Secret reaches your container decides everything about update behaviour. There are two ways, and they behave completely differently:

consumptionupdates on ConfigMap change?when
env: / envFrom: (env vars)neveronly on pod restart
volumeMounts: (mounted files)eventually, in placeafter the kubelet sync (about a minute), and only if the app re-reads the file

Environment variables are copied into the process at exec time. A running Linux process cannot have its environment changed from outside, so there is physically no way for Kubernetes to update them without starting a new process. That is not a Kubernetes limitation, it is how processes work. If your config comes in as env vars, a restart is the only path, full stop.

Volume-mounted ConfigMaps are different. The kubelet periodically syncs the projected files, so after roughly its sync period (about a minute by default, plus cache delays) the file inside the pod reflects the new content, with no restart. But your application has to actually watch that file and reload it. Most applications read config once at startup and never look again, so even though the file changed, the running app never notices. This is the detail that makes the volume case feel just as broken as the env-var case.

Why this is deliberate, not a missing feature

Kubernetes draws a hard line between storing configuration and rolling out a workload. A ConfigMap is a piece of data in the API. A Deployment is the thing that owns the rollout strategy: how many pods restart at once, the surge and unavailability budget, the readiness gates, the rollback behaviour. If editing a ConfigMap silently triggered a rollout, you would get an unmanaged, unversioned deployment every time someone fixed a typo in a value, with none of the safety you carefully configured on the Deployment. A shared ConfigMap touched by ten teams could restart production on a whim.

So the platform makes you say it explicitly: to change what is running, change the thing that manages what is running. The Deployment. That is the entire principle, and the checksum trick is just the polite way of telling the Deployment its desired state changed.

edit ConfigMap only ConfigMap updated in API pod template unchanged running pods keep old config + checksum/config hash of ConfigMap changes pod template now differs new ReplicaSet, managed rollout
The checksum does not configure anything. It just makes the pod template different so the Deployment does its normal, safe rollout.

The fix, step by step

Step 1: For a one-off change right now, force a rollout

kubectl rollout restart deployment/my-app
kubectl rollout status deployment/my-app

This is the correct manual command, not a hack. It patches the pod template with a fresh kubectl.kubernetes.io/restartedAt annotation, which triggers a normal rolling update that respects your surge and readiness settings. Expect deployment "my-app" successfully rolled out. Use this when you have already applied the ConfigMap and just need the new value live.

Step 2: For a repeatable pipeline, add a checksum annotation

The durable fix ties the rollout to the config content, so every helm upgrade that changes the ConfigMap automatically rolls the pods. In a Helm chart, add this to the pod template metadata inside the Deployment:

spec:
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
        checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}

Now when the rendered ConfigMap changes, its hash changes, the annotation changes, the pod template changes, and Helm's upgrade produces a new ReplicaSet. When the config is unchanged, the hash is identical and no needless rollout happens. This is the pattern the official Helm docs recommend for exactly this problem.

Step 3: For out-of-band edits, use a controller

If people edit ConfigMaps directly with kubectl edit rather than through your templates, a hash you compute at template time will not help, because you are not re-rendering. A controller such as Stakater Reloader watches ConfigMaps and Secrets and restarts the workloads that reference them. You opt a Deployment in with an annotation:

metadata:
  annotations:
    reloader.stakater.com/auto: "true"

Now any change to a ConfigMap or Secret this Deployment references triggers a rolling restart, no template hash required. It is the right tool when config lives independently of your deploy pipeline.

Verification

Confirm the new config is genuinely inside the running pod, not just in the API object:

# env-var config: read it from inside a live pod
kubectl exec deploy/my-app -- printenv LOG_LEVEL

# volume-mounted config: read the projected file
kubectl exec deploy/my-app -- cat /etc/app/config.yaml

# confirm a fresh rollout actually happened
kubectl rollout history deployment/my-app
kubectl get pods -l app=my-app -o wide   # AGE should be seconds, not days

The pods should show a young AGE and the value inside the container should match the ConfigMap. If printenv still shows the old value, the pod did not restart, so the template did not change; recheck the checksum annotation actually rendered a new hash.

What people get wrong

"Just wait, mounted ConfigMaps update eventually." True for the file, useless for the app. The projected file does sync after the kubelet period, but if your application read it once at startup, it will happily serve the stale value forever. Unless your app explicitly watches and reloads the file, waiting is not a fix.

Deleting pods by hand with kubectl delete pod. This works but throws away the rollout controls. You get no surge management, no ordered readiness, and if you delete them all at once you get downtime. kubectl rollout restart does the same job through the Deployment and keeps your availability guarantees. Deleting pods directly can also collide with the connection-draining behaviour I cover in why rolling updates still return connection refused.

Expecting env-var config to ever hot-reload. It cannot. If you genuinely need config that changes without a restart, mount it as a volume and write your app to watch the file, or read it from a store like a config service. Env vars are a start-time contract.

When it is still broken

  • The rollout happened but the app still uses old values. Check whether the value comes from a different source than you think: a baked-in default, a second ConfigMap, or a build-time value. This mirrors the front-end trap in why NEXT_PUBLIC_ vars are baked at build time, where the value is frozen into the artifact, not read at runtime.
  • The checksum never changes. If you hashed the wrong file path, or your CI does not re-render the template, the annotation stays constant and no rollout fires. Render locally with helm template and diff the annotation between the old and new config to confirm it moves.
  • New pods crash on the new config. If the rollout starts but pods fail, the value itself may be invalid. Work through it with the method in CrashLoopBackOff with no logs rather than reverting blindly.
  • The Secret is referenced across namespaces. A ConfigMap or Secret only exists within its namespace, and there is no native cross-namespace reference. If you expected one copy to update many namespaces, see why Kubernetes has no cross-namespace secrets.

Frequently asked questions

Does Kubernetes restart pods automatically when I edit a ConfigMap?
No. Editing a ConfigMap or Secret updates the object in the API but does nothing to the pods consuming it. Kubernetes deliberately separates configuration storage from deployment orchestration. Volume-mounted ConfigMaps eventually sync the file in place, but env-var-sourced values are baked in at container start and never update without a pod restart.
Why do my env vars from a ConfigMap never update even after waiting?
Because env vars are read once, when the container process starts, and copied into the process environment. Kubernetes has no mechanism to change a running process's environment. Updating the ConfigMap does not touch existing pods at all for env-var references, so no amount of waiting helps. You must trigger a new pod to pick up the new values.
What is the checksum/config annotation and how does it work?
It is a pod-template annotation whose value is a hash of the ConfigMap or Secret contents. When the config changes, the hash changes, which changes the pod template, which Kubernetes sees as a new desired state and rolls out fresh pods. The annotation value is never read by the app; its only job is to make the template differ so a rollout happens on config change.
Should I use a checksum annotation or a tool like Reloader?
Use the checksum annotation when you template manifests with Helm or Kustomize and want the rollout tied to your normal deploy flow with no extra components. Use a controller like Stakater Reloader when you edit ConfigMaps out of band, share config across many deployments, or do not template the hash yourself. Both achieve the same result: a rollout when the config changes.
#Kubernetes#ConfigMap#Helm#Secrets#Deployments
Keep reading

Related articles