Skip to content

ConfigMaps

RewrittenChapter 11 of 389 min read

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.

  • env with valueFrom.configMapKeyRef picks a single key. The variable name doesn't have to match the key, and nothing else from the ConfigMap leaks into the environment.
  • envFrom turns every key into a variable. The optional prefix is prepended to each name, so COLOR arrives as CFG_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 configMap volume writes each key as a file under mountPath, with permissions 0644 unless you set defaultMode. 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 itSees the change?
Volume mount (whole ConfigMap or items)Yes, eventually
Volume mount with subPathNo
env / envFromNo, until the container is replaced
Code watching the APIYes, 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 pod is the first place to look.
  • It exists, but in another namespace. kubectl get configmap without -n checks 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 subPath file 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 subPath and accept the update trade-off.
  • A key from binaryData is missing from the environment. Only data keys are passed through configMapKeyRef and configMapRef.
  • 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#