CoursesKubernetes fundamentalsConfigMaps: settings outside the image

ConfigMaps: settings outside the image

One image, many environments.

Beginner10 min · lesson 14 of 24
In plain terms
Non-secret config as env vars or files, outside the image.

The same container image should run in more than one environment. Settings that change per environment — hostnames, feature flags, non-secret ports — belong outside the image. A ConfigMap holds those values. You do not rebuild the image to change them.

A container image is a frozen, ready-to-run copy of the app and what it needs to start. You build it once and ship that copy everywhere. An environment is one of those places: laptop, a shared test server, or production. Settings that differ between environments — which database to connect to, how chatty the logs are — should not be baked into the image. Bake them in and you rebuild for every log-level change. In Kubernetes those values live in a ConfigMap: key/value pairs (for example LOG_LEVEL set to info) handed to the running app.

Kubernetes runs your app inside a Pod. A Pod is the smallest unit Kubernetes runs, really just a thin wrapper around one or more running containers. You'll talk to your cluster (the whole group of machines Kubernetes manages) using kubectl, the command-line tool for Kubernetes. Let's make a ConfigMap and hand it to a Pod.

Make one and look at it

app-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
app.conf: |
server_port = 8080
timeout = 30

That file is written in YAML, a plain-text format that describes configuration as indented key-value pairs. Two entries live under data. LOG_LEVEL is a single value your app can read. app.conf is a whole file: the vertical bar tells YAML to treat everything indented beneath it as one block of text, so you can paste a real config file straight in. Save it, then apply it.

apply the ConfigMap
kubectl apply -f app-config.yaml
output
configmap/app-config created
check it exists
kubectl get configmap app-config
output
NAME DATA AGE
app-config 2 9s

The DATA column shows 2, one for each key you put in. The ConfigMap now lives in the cluster on its own, separate from any app that might use it. It's just sitting there, ready for a Pod to ask for it.

Two ways into the app

A ConfigMap reaches the container two ways, and the difference matters in a minute. Environment variables are handed to the process at start. The other way is files: each key becomes a file the app can open later. Some apps want env vars. Others want a file at a path like /etc/app/app.conf. A ConfigMap can do both.

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: web
image: nginx:1.27
envFrom:
- configMapRef:
name: app-config
volumeMounts:
- name: cfg
mountPath: /etc/app
volumes:
- name: cfg
configMap:
name: app-config

A Deployment is the object that keeps a set of identical Pods running and restarts them for you when needed; replicas: 1 just means run a single copy. Two things wire the ConfigMap into the container. envFrom reads the ConfigMap's keys and turns them into environment variables. It only accepts valid variable names, so LOG_LEVEL comes through and app.conf is skipped, since a dot isn't allowed in a variable name. That's fine here, because we want app.conf as a file instead. That's the volume's job. Mounting means making those keys show up as files inside the container, here under /etc/app. Apply it, then check that both landed.

deploy it
kubectl apply -f web-deploy.yaml
output
deployment.apps/web created
read both values from inside the Pod
kubectl exec deploy/web -- printenv LOG_LEVEL
kubectl exec deploy/web -- cat /etc/app/app.conf
output
info
server_port = 8080
timeout = 30

printenv read the environment variable; cat read the file. Both values came out of the same ConfigMap, and neither one is baked into the nginx image. That's the whole point: one image, different settings, decided when it runs. Point a different ConfigMap at the same Deployment and the app behaves differently, no rebuild needed.

When the ConfigMap isn't there

The ConfigMap lives on its own, which means Kubernetes won't invent values it can't find. Point a container at a ConfigMap that isn't there, whether from a typo in the name or from applying the Pod before you create the ConfigMap, and it can't finish starting. Worth causing on purpose once, so you recognise the shape of it later. Here's a throwaway Pod that asks for a ConfigMap nobody ever made.

broken.yaml
apiVersion: v1
kind: Pod
metadata:
name: broken
spec:
containers:
- name: app
image: nginx:1.27
envFrom:
- configMapRef:
name: does-not-exist
apply it
kubectl apply -f broken.yaml
output
pod/broken created

Now list the Pod.

check on it
kubectl get pod broken
output
NAME READY STATUS RESTARTS AGE
broken 0/1 CreateContainerConfigError 0 15s

Not Running, but CreateContainerConfigError. That's the kubelet, the node agent you'll meet again in a moment, saying it tried to build the container's environment from a ConfigMap and found nothing to build from. get pod tells you it's stuck; it won't tell you why. For that, describe the Pod and read the events at the bottom.

ask why
kubectl describe pod broken
output (events section)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 20s default-scheduler Successfully assigned default/broken to node-1
Normal Pulled 6s (x3 over 19s) kubelet Container image "nginx:1.27" already present on machine
Warning Failed 6s (x3 over 19s) kubelet Error: configmap "does-not-exist" not found

The last line names it exactly: configmap "does-not-exist" not found. Create that ConfigMap and the kubelet retries on its own, no manual restart needed. Reading the last event after a describe is how you'll untangle most stuck Pods, not only this one. Clean up the throwaway with kubectl delete pod broken.

Changing a setting later

Here's the part that trips people up. When you edit a ConfigMap, the change doesn't reach a running Pod the same way for both methods. Which one you picked earlier decides how much work the update takes now. Values that went in as environment variables are read once, at startup, then frozen. The process already copied them into memory, so editing the ConfigMap does nothing to it. Values mounted as files do refresh on their own.

That refresh is handled by the kubelet, the Kubernetes agent running on every node (a node is simply a worker machine in your cluster). It updates mounted ConfigMap files roughly once a minute. Even then, your app only notices if it re-reads the file, and plenty of apps read their config just once at boot.

So when you change config and want it to take effect, you normally restart the app's Pods. With a Deployment that's one safe command, and Kubernetes brings up fresh Pods, replacing the old ones one at a time so traffic keeps flowing.

make a change take effect
kubectl rollout restart deployment/web
output
deployment.apps/web restarted
You changed a ConfigMap. Does the Pod see it?
ConfigMap updated
how did the value get into the Pod?
as an environment variable
restart the Pod (kubectl rollout restart) to pick it up
frozen when the container started
as a mounted file
takes effect only if the app re-reads the file
kubelet refreshes it after about a minute
Env-var config is a startup snapshot and needs a restart. File-mounted config updates by itself, but the app still has to re-read it.

ConfigMaps hold non-secret config as keys you inject as env vars or files. Prefer config outside the image so one image promotes across environments. That is the whole point versus rebuilding for every LOG_LEVEL change.

Updates to a ConfigMap do not always restart pods. Env-from values are typically fixed at container start; file mounts can update depending on the kubelet sync. Prefer an explicit rollout restart when config must apply now.

Ship the wrong database hostname in a ConfigMap and production still breaks: the image is fine, the cluster is fine, and the app is pointed at staging. Treat ConfigMap changes as production changes. Review them like code.

Try this

Create a ConfigMap, run a pod that prints an env var from it, then change the map and restart to pick up the new value.

terminal
$ kubectl create configmap app-config --from-literal=LOG_LEVEL=info
configmap/app-config created
$ kubectl run cfg --image=busybox:1.36 --restart=Never --env=LOG_LEVEL=unused --command -- sleep 300
pod/cfg created
$ kubectl set env pod/cfg --from=configmap/app-config
pod/cfg env updated
# recreate to see env-from cleanly:
$ kubectl delete pod cfg
$ kubectl run cfg --image=busybox:1.36 --restart=Never --overrides='{"spec":{"containers":[{"name":"cfg","image":"busybox:1.36","command":["sleep","300"],"envFrom":[{"configMapRef":{"name":"app-config"}}]}]}}'
pod/cfg created
$ kubectl exec cfg -- printenv LOG_LEVEL
info
$ kubectl create configmap app-config --from-literal=LOG_LEVEL=debug -o yaml --dry-run=client | kubectl apply -f -
configmap/app-config configured
$ kubectl delete pod cfg; kubectl run cfg --image=busybox:1.36 --restart=Never --overrides='{"spec":{"containers":[{"name":"cfg","image":"busybox:1.36","command":["sleep","300"],"envFrom":[{"configMapRef":{"name":"app-config"}}]}]}}'
$ kubectl exec cfg -- printenv LOG_LEVEL
debug
$ kubectl delete pod cfg; kubectl delete configmap app-config
pod "cfg" deleted
configmap "app-config" deleted

Takeaway

Keep non-secret settings in ConfigMaps, inject them, and roll pods when you need env changes now. Config mistakes are production incidents even when the image never changed.

Quick check
01You edit a ConfigMap value that a running Pod reads as an environment variable. What happens to that Pod?
Incorrect — No. Environment variables are set once, when the container starts, so a running process keeps the old value.
Correct — Env vars are read only at startup, so you roll the workload with kubectl rollout restart for the new value to take effect.
Incorrect — No. Editing a ConfigMap doesn't touch the running Pod, so there's nothing there to crash it.
Incorrect — No. Updating a ConfigMap only stores the new data; it never restarts Pods on its own.
02web-deploy.yaml wires app-config into the container with both envFrom and a volume mount. LOG_LEVEL appears as an environment variable but app.conf does not. Why?
Incorrect — Size is not the reason; the block is small, and the issue is the key's name.
Incorrect — envFrom reads every key that is a valid variable name, not just the first.
Incorrect — The two paths are independent; a mount does not stop envFrom from reading the same key.
Correct — LOG_LEVEL is a legal variable name so it comes through, while app.conf's dot disqualifies it (it is exposed as a file instead).
03A Pod that references a ConfigMap is stuck at CreateContainerConfigError, and kubectl describe shows the event 'configmap "app-config" not found'. What is happening, and what occurs once you create that ConfigMap?
Correct — the container cannot build its environment from a missing ConfigMap, but the kubelet keeps retrying and starts the Pod as soon as the ConfigMap appears.
Incorrect — The event names the ConfigMap, not the image; describe even shows the image was already present.
Incorrect — No manual delete is required; the kubelet retries automatically once the ConfigMap exists.
Incorrect — 'not found' means the object is missing entirely, not that it is empty.
ConfigMaps are plain text, so keep passwords out
Anyone who can read a ConfigMap sees every value in it as plain text, with no encryption at all. That's fine for log levels, web addresses, and other non-secret settings, but it's the wrong home for passwords, API keys, or tokens. Those go in Secrets, which you'll meet in the next lesson. And remember: values injected as environment variables are frozen at startup, so a ConfigMap edit needs a Pod restart to take hold.

Related