Kubernetes ConfigMaps
Create ConfigMaps from literals, files or YAML and consume them as environment variables or mounted files, including why updates sometimes do not appear.
A ConfigMap holds non-sensitive configuration as key-value pairs so your application reads settings at runtime rather than having them compiled into the image. One image, many environments.
It is a plain data object with no behaviour of its own. Everything interesting happens in how a pod consumes it.
Creating a ConfigMap
From literal values
kubectl create configmap app-config \
--from-literal=APP_ENV=dev \
--from-literal=LOG_LEVEL=info
Good for a handful of scalars. Beyond that it becomes unreadable and unreviewable.
From a file
kubectl create configmap nginx-config --from-file=nginx.conf
The key becomes the filename (nginx.conf) and the value is the entire file contents. Point it at a directory and every file in it becomes a key, which is the tidiest way to ship a set of config files.
Override the key name when the filename is not what you want inside the container:
kubectl create configmap nginx-config --from-file=default.conf=nginx.prod.conf
From YAML
This is the form you should keep in version control, because it is reviewable and reproducible:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: dev
LOG_LEVEL: info
# A multi-line file as a single key
nginx.conf: |
server {
listen 8080;
location / {
proxy_pass http://payments-api:80;
}
}
Two notes on data. Values must be strings, LOG_LEVEL: 8080 or DEBUG: true is rejected by the API server, so quote them: "8080", "true". And the | block scalar is how you embed an entire file, preserving newlines.
For binary content there is a separate binaryData field taking base64 values.
Consuming as environment variables
Every key at once with envFrom
apiVersion: v1
kind: Pod
metadata:
name: env-config-demo
spec:
containers:
- name: app
image: busybox:1.36
command: ['sh', '-c', 'env | grep APP_ && sleep 3600']
envFrom:
- configMapRef:
name: app-config
Convenient, but it imports every key, including any that are not valid environment variable names, which are skipped silently. It also makes it hard to tell from the pod spec which variables the application actually depends on.
Mark it optional if the pod should still start without the ConfigMap:
envFrom:
- configMapRef:
name: app-config
optional: true
Without optional: true, a missing ConfigMap leaves the pod stuck in CreateContainerConfigError, which kubectl describe pod will tell you plainly.
A single key with valueFrom
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
More verbose, and usually the better choice: the pod spec documents exactly what the application needs, and you can rename a key on the way in.
Consuming as mounted files
apiVersion: v1
kind: Pod
metadata:
name: file-config-demo
spec:
volumes:
- name: config-volume
configMap:
name: app-config
containers:
- name: app
image: busybox:1.36
command: ['sh', '-c', 'ls /config && sleep 3600']
volumeMounts:
- name: config-volume
mountPath: /config
Every key becomes a file under /config, with the value as its contents.
Mounting a volume at a path replaces whatever was in that directory in the image. To place a single file into an existing directory, project just the keys you want:
volumes:
- name: config-volume
configMap:
name: app-config
items:
- key: nginx.conf
path: default.conf
The other approach is subPath, which mounts one file without hiding the directory, but it comes with a significant catch covered below.
Why your ConfigMap change did not take effect
This is the most common ConfigMap question, and there are three distinct causes.
1. It is consumed as an environment variable. A process receives its environment once, at start. Nothing updates it afterwards. Restart the pods:
kubectl rollout restart deployment/payments-api
2. It is mounted with subPath. A subPath mount is resolved once at container start and never refreshes, unlike a normal volume mount which the kubelet keeps in sync. If you need live updates, mount the volume normally and use items to control filenames.
3. It is a normal volume mount and you are impatient. These do update, but not instantly. The kubelet refreshes on its sync period, typically up to about a minute, plus cache propagation. The file changes; your application still has to notice, which means either watching the file or being sent a signal.
A useful pattern that sidesteps all of this: version the ConfigMap name (app-config-v4), mark it immutable: true, and reference the new name from the Deployment. The config change then flows through a normal rolling update, appears in your rollout history, and rolls back with everything else.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v4
immutable: true
data:
LOG_LEVEL: debug
Inspecting
kubectl get configmaps
kubectl describe configmap app-config
kubectl get configmap app-config -o yaml
# One key's value, cleanly
kubectl get configmap app-config -o jsonpath='{.data.LOG_LEVEL}'
# What the container actually sees
kubectl exec -it <pod> -- env | sort
kubectl exec -it <pod> -- cat /config/nginx.conf
That last pair is worth reaching for early. Checking what the container sees, rather than what the manifest says, resolves most configuration arguments in seconds.
Quick reference
| Task | Command |
|---|---|
| From literals | kubectl create configmap <n> --from-literal=K=V |
| From a file | kubectl create configmap <n> --from-file=path |
| From a directory | kubectl create configmap <n> --from-file=dir/ |
| From an env file | kubectl create configmap <n> --from-env-file=.env |
| Show all keys | kubectl describe configmap <n> |
| One value | kubectl get cm <n> -o jsonpath='{.data.KEY}' |
| Edit in place | kubectl edit configmap <n> |
| Apply an env change | kubectl rollout restart deployment/<n> |
Key takeaways
- Values must be strings. Quote numbers and booleans or the API server rejects the manifest.
envFromimports everything;valueFromimports one key. PrefervalueFromso the pod spec documents its dependencies.- Mounting a volume replaces the target directory. Use
itemsto project selected keys with chosen filenames. - Env vars never refresh,
subPathmounts never refresh, normal volume mounts refresh in about a minute. - A missing non-optional ConfigMap gives
CreateContainerConfigError:describe podnames it. - Version the name and set
immutable: trueso config changes ride your normal rollout and rollback.
Check your understanding
A file that never updates
A ConfigMap is mounted with subPath to place one config file into /etc/nginx. Editing the ConfigMap updates it in the API, but the file in the running container never changes. Why?
A rejected manifest
Applying a ConfigMap with "MAX_CONNECTIONS: 100" in its data block fails with a type error. What is wrong?
Frequently Asked Questions
How do I create a ConfigMap from a file?
kubectl create configmap <name> --from-file=path/to/file creates one key named after the file, with the whole file as its value. Pointing --from-file at a directory makes a key per file in it. Use --from-file=wanted-name=path/to/file to control the key name, and --from-env-file=.env to turn a KEY=VALUE file into separate keys. For anything you intend to keep, generate the YAML with --dry-run=client -o yaml and commit that instead of running the imperative command against the cluster.
Why is my pod not seeing updated ConfigMap values?
There are three possibilities. If the value is injected as an environment variable, it can never update in a running process. Restart with kubectl rollout restart deployment/<name>. If the volume uses subPath, it is mounted once and never refreshed; mount the volume normally and use items to set the filename. If it is a normal volume mount, updates do arrive but take up to roughly a minute, and your application still has to reread the file. Many need a signal or a restart to notice.
What is the difference between envFrom and valueFrom?
envFrom with configMapRef imports every key in the ConfigMap as an environment variable, which is concise but hides what the application actually depends on and silently skips keys that are not valid variable names. valueFrom with configMapKeyRef pulls one named key and lets you give it a different variable name. Prefer valueFrom for anything important, because the pod spec then documents the contract between the application and its configuration.
Can I store a whole configuration file in a ConfigMap?
Yes, and it is a common pattern. Use a YAML block scalar so newlines survive, then mount the ConfigMap as a volume so the key appears as a file. Be aware that mounting a volume at a directory replaces that directory's existing contents from the image. Use the items field to project only the keys you want, with the filenames you want. The 1 MiB ConfigMap limit applies, which is ample for configuration but not for data files.
Is there a size limit on a ConfigMap?
1 MiB, imposed by etcd, covering all keys and values together. Large ConfigMaps are also a real load on the API server, because every kubelet consuming one watches it for changes. If you are close to the limit the content probably is not configuration. Fetch it from object storage at start-up, bake it into the image, or use a volume. Marking large ConfigMaps immutable: true removes the watch overhead entirely.
Should I use immutable: true?
Usually yes, paired with a versioned name such as app-config-v4. It makes accidental in-place edits of production configuration impossible, and it lets the kubelet stop watching the object, which noticeably reduces API server load in large clusters. The trade-off is that a change means creating a new ConfigMap and updating the Deployment to reference it, which is arguably the point, since the config change then appears in your rollout history and rolls back like any other change.