ConfigMaps
Rewritten for this edition in October 2026 for Kubernetes 1.37. Commands checked against the official documentation.
A Kubernetes ConfigMap is an API object that holds non-confidential configuration as key-value pairs, so the same container image can run in development, staging and production with different settings. A Pod reads a ConfigMap in one of three ways: as environment variables, as files in a read-only volume, or through command arguments built from those variables. Code can also query the Kubernetes API for it directly. Two limits frame everything else. The data can't exceed 1 MiB, and a ConfigMap gives you no secrecy or encryption, so passwords and tokens belong in a Secret.
This chapter builds one small app and wires a ConfigMap into it every way the API allows. Then it changes the config and watches which parts of the Pod notice.
What a ConfigMap holds, and what it doesn't#
Most objects you've met so far have a spec. A ConfigMap has two optional maps instead: data for UTF-8 strings and binaryData for base64-encoded bytes. Keys in either map may only use letters, digits, -, _ and ., and the same key can't appear in both. Kubernetes doesn't care whether a value is a single word or a whole nginx config pasted in as a multi-line block. What changes the behaviour is how a Pod consumes it.
Three rules from the ConfigMap concept page catch people early:
- The Pod and the ConfigMap must live in the same namespace. Code that talks to the API itself is the one exception, because it can read a ConfigMap anywhere it has permission to.
- Static Pods (the ones the kubelet starts from manifest files on a node) can't reference ConfigMaps at all.
- The 1 MiB ceiling is for the whole object. Larger configuration should come from a volume, a database or a file service.
If you skipped the Namespaces chapter, read it now; every command below runs in a namespace of its own so cleanup is one line.
Before you start#
You need a running cluster and kubectl pointed at it. A local KinD cluster or K3s is plenty. Make a working directory, then a namespace for the lab:
mkdir configmap-lab && cd configmap-lab
kubectl create namespace cm-lab
Create a ConfigMap with kubectl#
The imperative form is kubectl create configmap <name> <data-source>, and the name has to be a valid DNS subdomain name. There are three data sources, and you can repeat each flag as many times as you need.
From literal values#
Good for a handful of short settings. Each --from-literal becomes one key in data:
kubectl create configmap greeter-settings -n cm-lab \
--from-literal=LOG_LEVEL=info \
--from-literal=GREETING=hello
From a file or a directory#
When the app expects a real config file, hand kubectl the file. The key defaults to the file's basename and the value is the whole file:
cat <<EOF > app.properties
greeting.target=world
greeting.repeat=2
EOF
kubectl create configmap greeter-files -n cm-lab \
--from-file=app.properties --save-config
That --save-config flag stores the object's configuration in an annotation, which matters later when you update this ConfigMap with kubectl apply. You can pick a different key with --from-file=<key>=<path>, for example --from-file=settings.conf=app.properties.
Point --from-file at a directory and kubectl packages every regular file whose name is a valid key. Subdirectories, symlinks, devices and pipes are ignored. Watch the filenames here: a file with a character outside the allowed set can be skipped without any error printed.
From an env file#
An env file holds one VAR=VAL per line. Lines starting with # and blank lines are ignored. Quotes get no special handling, so they end up inside the value:
cat <<EOF > greeter.env
# feature switches
COLOR=blue
BETA="true"
EOF
kubectl create configmap greeter-env -n cm-lab --from-env-file=greeter.env
Since kubectl v1.23 you can pass --from-env-file more than once to merge several files into one ConfigMap.
Look at what you made#
kubectl get configmaps -n cm-lab
kubectl describe configmap greeter-files -n cm-lab
kubectl get configmap greeter-env -n cm-lab -o yaml
The YAML output for greeter-env looks like this (metadata trimmed). Note the quoted value: BETA really is the five-character string with quotes around it.
apiVersion: v1
kind: ConfigMap
metadata:
name: greeter-env
namespace: cm-lab
data:
BETA: '"true"'
COLOR: blue
The same thing as YAML#
For anything you keep in Git, write the manifest. This one mixes a property-style key, a file-style key written as a YAML block scalar, and a binaryData key holding the eight bytes of a PNG file signature:
apiVersion: v1
kind: ConfigMap
metadata:
name: greeter-yaml
namespace: cm-lab
data:
LOG_LEVEL: "debug"
app.properties: |
greeting.target=cluster
greeting.repeat=3
binaryData:
marker.png: iVBORw0KGgo=
Quote numbers and booleans under data ("3", "true"); the field only holds strings. Apply it with kubectl apply -f greeter-yaml.yaml after saving it. When kubectl builds a ConfigMap from a file that isn't ASCII or UTF-8, it puts that file under binaryData for you, and kubectl get configmap greeter-yaml -n cm-lab -o jsonpath='{.binaryData}' shows those keys.
Feed the ConfigMap to a Deployment#
Here's a small Deployment that uses all three consumption styles at once. Save it as greeter.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: greeter
namespace: cm-lab
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: greeter
template:
metadata:
labels:
app.kubernetes.io/name: greeter
spec:
containers:
- name: greeter
image: alpine:3
command:
- /bin/sh
- -c
- while true; do echo "level=$LOG_LEVEL color=$CFG_COLOR";
cat /etc/greeter/app.properties; sleep 15; done;
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: greeter-settings
key: LOG_LEVEL
envFrom:
- prefix: CFG_
configMapRef:
name: greeter-env
volumeMounts:
- name: greeter-config
mountPath: /etc/greeter
readOnly: true
volumes:
- name: greeter-config
configMap:
name: greeter-files
Each block does a different job.
envwithvalueFrom.configMapKeyRefpicks a single key. The variable name doesn't have to match the key, and nothing else from the ConfigMap leaks into the environment.envFromturns every key into a variable. The optionalprefixis prepended to each name, soCOLORarrives asCFG_COLOR. Since the prefix is plain text, it's also a cheap way to keep one ConfigMap's keys from colliding with another's.- The
configMapvolume writes each key as a file undermountPath, with permissions0644unless you setdefaultMode. A ConfigMap volume is always read-only.
Apply it and check all three:
kubectl apply -f greeter.yaml
kubectl rollout status deployment/greeter -n cm-lab
kubectl logs deployment/greeter -n cm-lab
kubectl exec -n cm-lab deploy/greeter -- ls /etc/greeter
The log lines look like this:
level=info color=blue
greeting.target=world
greeting.repeat=2
Variables can also go into command and args with the $(VAR_NAME) syntax, which Kubernetes expands before the container starts. That's the fourth style from the docs, and it rides on the same env entries.
Mount only some keys, or rename them#
Without items, every key becomes a file. With it, only the keys you list are projected, under the paths you choose. A listed key that doesn't exist makes the volume setup fail unless the source is marked optional.
volumes:
- name: greeter-config
configMap:
name: greeter-files
items:
- key: app.properties
path: conf/greeter.properties
Paths are relative and can't contain ... With this volume the file appears at /etc/greeter/conf/greeter.properties.
Dropping one file into an existing directory#
Mounting a ConfigMap volume over a directory hides whatever the image already had there. Mount at /etc and the rest of /etc is gone from the container's view. The usual workaround is subPath, which places a single key as a single file:
volumeMounts:
- name: greeter-config
mountPath: /etc/nginx/conf.d/greeter.conf
subPath: app.properties
It works, with one cost you need to remember for the next section: a container that uses a ConfigMap through subPath never receives updates to it.
What happens when you change a ConfigMap#
This is where ConfigMaps surprise people. Change the file and regenerate the object. The --dry-run=client -o yaml pair prints the new manifest without sending it, and kubectl apply -f - reads it from stdin:
sed -i.bak 's/greeting.repeat=2/greeting.repeat=5/' app.properties
kubectl create configmap greeter-files -n cm-lab \
--from-file=app.properties --dry-run=client -o yaml | kubectl apply -n cm-lab -f -
For a quick one-off, kubectl edit configmap greeter-files -n cm-lab opens the object in your editor. Either way, here's how each consumer reacts:
| How the Pod uses it | Sees the change? |
|---|---|
Volume mount (whole ConfigMap or items) | Yes, eventually |
Volume mount with subPath | No |
env / envFrom | No, until the container is replaced |
| Code watching the API | Yes, as soon as it handles the watch event |
The delay for mounted files comes from the kubelet. It checks mounted ConfigMaps on every periodic sync (syncFrequency, one minute by default) and reads them through a local cache whose behaviour is set by configMapAndSecretChangeDetectionStrategy (Watch by default, with Cache and Get as the alternatives). The worst case is the sync period plus that cache's propagation delay. Updating one of the Pod's annotations triggers an immediate refresh. Keep running kubectl logs deployment/greeter -n cm-lab and the greeting.repeat line flips to 5 within that window, while level= and color= stay put.
Your app also has to reread the file. Kubernetes swaps the content on disk; it can't make a process that parsed its config once at startup look again.
Make the Pods pick up the change#
Environment variables are fixed when a container starts. To push new values in, replace the Pods:
kubectl rollout restart deployment/greeter -n cm-lab
kubectl rollout status deployment/greeter -n cm-lab
The restart creates a new ReplicaSet, so old Pods terminate and new ones start with the current ConfigMap. Pods created for any other reason, such as a scale-up, also read the latest values, which means a Deployment you forget to restart can end up with old and new configuration running side by side.
Immutable ConfigMaps#
Set immutable: true and the API server refuses any change to data or binaryData; only metadata stays editable. The feature has been stable since v1.21. It protects you from an accidental edit taking down every Pod that reads the file, and in clusters with tens of thousands of ConfigMap-to-Pod mounts it cuts load on kube-apiserver, because the kubelet stops watching immutable ones.
apiVersion: v1
kind: ConfigMap
metadata:
name: greeter-settings-v2
namespace: cm-lab
data:
LOG_LEVEL: warn
GREETING: hi
immutable: true
There's no way back. You can't make an immutable ConfigMap mutable again; you can only delete it and create it anew, and the docs recommend recreating Pods that still mount the deleted one. In practice that leads to versioned names. Ship greeter-settings-v2, change the Deployment to reference it (which triggers a rollout by itself), and delete v1 once nothing uses it.
Kustomize automates exactly that pattern. Its configMapGenerator appends a hash of the content to the ConfigMap's name and rewrites references in your Deployment to match, so every config change produces a new name and a rollout. The Kustomize chapter covers the tool; the kustomization task page has the generator syntax.
Common mistakes#
Each of these is easy to hit on a first cluster, and a few fail quietly.
- The Pod sits in a non-running state after you deploy. A referenced ConfigMap, or a key in one, doesn't exist. Kubernetes won't start the Pod until it does, unless the reference has
optional: true, in which case the variable or volume is just empty.kubectl describe podis the first place to look. - It exists, but in another namespace.
kubectl get configmapwithout-nchecks your current namespace only. - A password went into a ConfigMap. Anyone allowed to read ConfigMaps in that namespace can read it in plain text. Move it to a Secret.
- The config file updated, the env var didn't. Expected behaviour. Run
kubectl rollout restart. - The
subPathfile never updates. Also expected. Mount the whole ConfigMap into its own directory if live updates matter. - The image's own files vanished. The volume mount hid the directory it landed on. Mount somewhere unused, or use
subPathand accept the update trade-off. - A key from
binaryDatais missing from the environment. Onlydatakeys are passed throughconfigMapKeyRefandconfigMapRef. - A value has stray quotes. It came from an env file, where quotes are kept literally.
- A file from the directory is missing. Its name had a character kubectl doesn't accept as a key, and it was skipped without an error.
Clean up#
Deleting the namespace removes the Deployment and every ConfigMap in it:
kubectl delete namespace cm-lab
cd .. && rm -r configmap-lab
Common questions#
Two that come up once the basics work.
Can a Pod read a ConfigMap from another namespace?#
Not through env, envFrom or a volume. Code in the Pod that calls the Kubernetes API can, as long as its service account is allowed to read ConfigMaps there, and it can subscribe to changes too.
Should every setting move into a ConfigMap?#
No. The task guide's advice is that ConfigMaps should reference properties files without replacing them, much like files under /etc on a Linux host. Keep defaults in the image and put only what differs between environments into the ConfigMap; a ConfigMap per environment is normal, a ConfigMap per setting isn't.
Where this fits in the book#
The next chapter, Kubernetes Service, gives the greeter a stable address. For the full set of consumption patterns, the official Configure a Pod to Use a ConfigMap task and the Updating Configuration via a ConfigMap tutorial are worth keeping open, along with the kubectl create configmap reference.
Further watching#
- Video by Anais Urlichs (the original 100 Days of Kubernetes series)