CoursesKubernetes administrationConfigMaps & environment

ConfigMaps & environment

Injecting configuration as env vars and files.

Intermediate10 min · lesson 15 of 65
In plain terms
A ConfigMap is the settings sheet taped to a machine — same machine everywhere, different settings per location. You change the sheet, never the machine itself.

You build a container image once. Then you ask that same image to run on your laptop, in staging, and in production, and to behave differently in each place without a single rebuild. So what changes between those places? Configuration. Which database to point at. How loud the logs should be. Whether a half-finished feature is switched on. Bake those into the image and every tweak means a fresh build, a new tag, and a redeploy just to flip one log level. A ConfigMap is where that configuration lives instead, so none of it gets welded into the image.

Think of the image as a coffee machine that ships identical to every cafe. The ConfigMap is the little card each cafe slots in: these beans, this temperature, this strength. Same machine everywhere, different card, different cup. Change the card and the next cup changes; nothing about the machine itself moves. In Kubernetes terms a ConfigMap is a namespaced object (it lives inside one namespace, the way a file lives inside one folder) that holds configuration as plain key-value pairs. Non-sensitive values only. Passwords, tokens, and API keys belong in a Secret, which is the next lesson.

Two ways to make one

You rarely hand-write the YAML for these. Two flags on kubectl create cover most of what you need. Use --from-literal for a value you type inline, and --from-file to pull a whole file off disk and store it under a key. Whichever flag you reach for, you end up with the same kind of object; only the keys differ.

create a ConfigMap two ways at once
kubectl create configmap app-config \
--from-literal=LOG_LEVEL=info \
--from-file=app.yaml=./app.yaml
output
configmap/app-config created

Read it back and you can see exactly what got stored. The file landed as a block of text under its key. The literal is just a string.

inspect what landed in etcd
kubectl get configmap app-config -o yaml
output
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: default
data:
LOG_LEVEL: info
app.yaml: |
server:
port: 8080

Getting it into a container

A ConfigMap on its own does nothing. It just sits there. To matter, it has to reach the process inside your Pod (the smallest thing Kubernetes runs: one or more containers that share a single network address). There are two doors into that process. The first is environment variables, the named settings a program reads the moment it launches. envFrom turns every key in the map into one variable; configMapKeyRef pulls a single key in under a variable name you choose, for when you only want one value. The second door is files: mount the map as a volume and each key becomes a file on a path you pick. A rule of thumb: reach for env vars when the app was written to read settings from its environment, and reach for a volume when it expects a real config file at a known path, like an nginx.conf or an application.yaml it opens from disk. Plenty of apps use both at once, which is exactly what the Deployment below does.

web-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: app
image: nginx:1.27
envFrom:
- configMapRef:
name: app-config
volumeMounts:
- name: cfg
mountPath: /etc/app
volumes:
- name: cfg
configMap:
name: app-config

Apply that, then look inside the running container to check both doors worked. One quirk shows up right away. envFrom quietly skips app.yaml, because a dot isn't a legal character in an environment variable's name, so that key shows up only as the mounted file. Kubernetes even records an event about the skipped key if you go looking for it.

verify env var and mounted file
kubectl exec deploy/web -- sh -c 'echo "LOG_LEVEL=$LOG_LEVEL"; cat /etc/app/app.yaml'
output
LOG_LEVEL=info
server:
port: 8080

What a change actually does

Here is where administrators get burned. How a ConfigMap edit reaches a running Pod depends entirely on which door you used, and the two doors behave in opposite ways. Environment variables are a snapshot. The operating system hands each program its set of variables exactly once, at the instant it starts, then never touches them again. Edit the ConfigMap and the running container's env vars don't budge. Files are the opposite. The kubelet (the Kubernetes agent running on every node, the actual machine that hosts your Pods) keeps the mounted volume in sync with the object. On its regular sync loop it writes the new content into a fresh hidden directory, then flips a symlink named ..data to point at that new directory in a single step. Think of a stagehand swapping the whole set behind a curtain: the container never catches a file half-written mid-change. Budget up to about a minute of lag. And the app only benefits if it actually re-reads the file on its own.

change both keys at once
kubectl patch configmap app-config --type merge \
-p '{"data":{"LOG_LEVEL":"debug","app.yaml":"server:\n port: 9090\n"}}'
output
configmap/app-config patched

Wait a minute, then run the same check again. The mounted file has changed. The env var has not.

same command, one minute later
kubectl exec deploy/web -- sh -c 'echo "LOG_LEVEL=$LOG_LEVEL"; cat /etc/app/app.yaml'
output
LOG_LEVEL=info
server:
port: 9090

To move the env-injected value, you have to replace the Pod. kubectl rollout restart does it cleanly: it brings up new Pods that read the fresh snapshot at startup, then drains the old ones away. It feels like a workaround the first time you see it, but it is the standard move, because env config is fixed in a live container, full stop. Teams automate this so nobody has to remember. Stamp a hash of the ConfigMap into the Pod template as an annotation, and any edit to the map changes the hash, which changes the template, which makes the Deployment roll all by itself.

force new Pods to pick up new env vars
kubectl rollout restart deploy/web
output
deployment.apps/web restarted

When it's missing, and when it must not move

If a container references a ConfigMap, or a single key inside one, that isn't there, the kubelet can't finish assembling the container's configuration and the Pod parks in CreateContainerConfigError. That one status covers both cases: a whole ConfigMap that doesn't exist, and a single missing key inside one that does. kubectl describe pod names the exact key it couldn't find, which is usually all you need. Either create the key, or set optional: true on the reference if the app can start without it. At the other extreme, once a ConfigMap has settled and you know it won't change, you can set immutable: true. That blocks every edit (to change it now you delete and recreate), and it tells the kubelet to stop watching the object. In a cluster running thousands of Pods, all those dropped watches lift real load off the API server, the control-plane process every kubectl command talks to.

Does the running Pod see your ConfigMap change?
You edited a ConfigMap. Does the running Pod see it?
the answer is set entirely by how you injected it
env var (envFrom / configMapKeyRef)
No. Frozen at container start
roll the Pods: kubectl rollout restart
whole-volume mount
Yes. kubelet swaps ..data in
~1 min lag; app must re-read the file
volume mount with subPath
No. Never updates
looks live in the YAML, silently isn't
Files updated live only for a whole-volume mount. Env vars and subPath mounts are snapshots taken at start; changing them means replacing the Pod.
A subPath mount looks live but freezes
Mount a whole ConfigMap volume at /etc/app and the kubelet keeps those files fresh. Mount a single key with subPath (volumeMounts with subPath: app.yaml, so you can drop one file in next to others the image already ships) and it silently stops updating. subPath copies the file in once, at container start, and never touches it again, exactly like an env var. The YAML looks almost identical to the live version, so people wire config up this way, edit the ConfigMap, watch nothing happen, and lose an afternoon to it. Need live reload? Mount the whole volume at its own path. Stuck with subPath? Treat that file as frozen and roll the Pods whenever it changes.

Immutable ConfigMaps prevent silent drift. Use them when you want deploys, not live edits, to change behavior.

Huge ConfigMaps hurt the API and etcd. Keep blobs in object storage and put pointers in config.

Name keys carefully. Apps that require exact filenames want items with path mappings, not the whole map dumped blindly. Immutable ConfigMaps prevent silent drift.

Try this

Create a ConfigMap from literals and a file, mount it into a pod, and also inject one key as an env var. Change the ConfigMap and note what updates live versus what needs a restart.

terminal
$ kubectl create configmap app-config \
--from-literal=LOG_LEVEL=info \
--from-file=app.yaml=./app.yaml
$ kubectl get configmap app-config -o yaml
$ kubectl exec deploy/web -- sh -c 'echo "LOG_LEVEL=$LOG_LEVEL"; cat /etc/app/app.yaml'
$ kubectl patch configmap app-config --type merge \
-p '{"data":{"LOG_LEVEL":"debug","app.yaml":"server:\n port: 9090\n"}}'
$ kubectl exec deploy/web -- sh -c 'echo "LOG_LEVEL=$LOG_LEVEL"; cat /etc/app/app.yaml'
$ kubectl rollout restart deploy/web

Takeaway

ConfigMaps carry non-secret config as env or files. File mounts can update; env vars generally need a new pod. Do not put credentials here.

Quick check
01A Deployment's Pods won't start. kubectl get pods shows STATUS CreateContainerConfigError, and kubectl describe pod ends with: couldn't find key API_HOST in ConfigMap default/app-config. What's the real fix?
Incorrect — The image is fine. The kubelet never got as far as running it; it failed while assembling the container's config from a key that isn't there.
Correct — A configMapKeyRef pointing at a key that doesn't exist blocks container creation. Supply the key, or mark the reference optional so the kubelet skips it.
Incorrect — Pods don't read ConfigMaps through RBAC; the kubelet mounts them on the node's behalf. A permissions problem would look nothing like a missing-key message.
Incorrect — Syncing can't create a key that was never in the ConfigMap. It stays in CreateContainerConfigError until the reference resolves.
02A ConfigMap holds two keys, LOG_LEVEL and app.yaml. You pull it in with envFrom. Inside the running container LOG_LEVEL is set, but there is no app.yaml environment variable. Why?
Incorrect — envFrom imports every eligible key, not just the first one.
Correct — envFrom turns each key into an env var, but app.yaml can't be a valid variable name, so it's dropped; that key is reachable only as a mounted file.
Incorrect — env vars can hold multi-line strings; the problem is the dot in the key name, not the value.
Incorrect — envFrom reads the data field; the skip happens purely because the key name isn't a valid env var identifier.
03You mount one ConfigMap key into an image's existing config directory using volumeMounts with subPath: app.yaml. You edit the ConfigMap and wait several minutes, but the file inside the container never changes. What's going on?
Incorrect — no sync interval helps, because a subPath mount is never kept in sync at all.
Incorrect — subPath doesn't use the ..data symlink mechanism; it copies the file once and never revisits it.
Correct — subPath freezes the file at start; only a whole-volume mount tracks ConfigMap edits, so switch to that or restart the Pods when the value changes.
Incorrect — immutable only blocks edits to the object; it isn't why a subPath file stays frozen.

Related