ConfigMaps

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

A container image should be the same artifact in every environment; what differs between development, staging and production — a database host, a log level, a feature flag, a whole configuration file — should be injected from outside. A ConfigMap is the Kubernetes object for that injection: a namespaced bag of key/value pairs holding non-confidential configuration, which Pods consume as environment variables, command-line arguments or files. Confidential values belong in a Secret instead (Secrets). This page is written against Kubernetes 1.37; see the section index for the full version baseline. It builds on Pods and is used by almost every workload page that follows, for example the log agent in DaemonSets and the initialization script in StatefulSets.

A ConfigMap has two payload maps: data (UTF-8 strings) and binaryData (base64-encoded bytes). Key names may contain only alphanumerics, -, _ and .. Because it is an ordinary API object, configuration is versioned, diffable and applied with the same kubectl apply workflow as the workloads that read it.

Creating ConfigMaps

There are four ways to create one, and they produce the same object shape. The imperative forms are convenient for experiments and for generating YAML (--dry-run=client -o yaml); the declarative YAML form is what belongs in version control.

# 1. Literal key/value pairs
kubectl create configmap catalog-config \
  --from-literal=LOG_LEVEL=info \
  --from-literal=PAGE_SIZE=25 \
  --namespace bookshelf

# 2. From a file: the key is the file name, the value the whole file content
kubectl create configmap catalog-files --from-file=application.properties --namespace bookshelf

# 2b. From a file under an explicit key, or from a whole directory (one key per file)
kubectl create configmap catalog-files --from-file=app.properties=./config/application.properties
kubectl create configmap catalog-dir --from-file=./config/

# 3. From an env file: each KEY=value line becomes one key
kubectl create configmap catalog-env --from-env-file=catalog.env

# 4. Declaratively, from a manifest (see below), then
kubectl apply -f catalog-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: catalog-config
  namespace: bookshelf
data:
  LOG_LEVEL: info
  PAGE_SIZE: "25"               # values are strings: quote numbers and booleans
  application.properties: |
    server.port=8080
    catalog.cache.enabled=true

Where the book is outdated

The 2017 Kubernetes: Up and Running sampler creates its Ghost ConfigMap with a command of the form kubectl apply cm --from-file ghost-config.js ghost-config. That is an erratum, not just an age problem: kubectl apply consumes manifests (-f/-k) and has no --from-file flag for building an object, and cm is only a resource short name. The command that creates a ConfigMap is kubectl create configmap ghost-config --from-file=ghost-config.js (short form kubectl create cm). The same sampler’s Redis chapter builds a multi-file ConfigMap, which is exactly what --from-file=<directory> or several --from-file flags produce.

Consuming a ConfigMap

A Pod can consume a ConfigMap in four ways. All of them require the ConfigMap to exist in the same namespace as the Pod; a missing ConfigMap blocks the container from starting (CreateContainerConfigError) unless the reference is marked optional: true.

Environment variables: env and envFrom

valueFrom.configMapKeyRef picks one key and renames it; envFrom.configMapRef imports every key at once (optionally with a prefix). Keys that are not valid environment-variable names are skipped with an event.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog-api
  namespace: bookshelf
spec:
  replicas: 2
  selector:
    matchLabels:
      app: catalog-api
  template:
    metadata:
      labels:
        app: catalog-api
    spec:
      containers:
        - name: catalog-api
          image: ghcr.io/example/bookshelf-catalog-api:1.0.0
          env:
            - name: CATALOG_LOG_LEVEL
              valueFrom:
                configMapKeyRef:
                  name: catalog-config
                  key: LOG_LEVEL
          envFrom:
            - configMapRef:
                name: catalog-config
              prefix: CATALOG_
          # $(VAR) references let an env var appear in command arguments
          args: ["--page-size=$(CATALOG_PAGE_SIZE)"]

Environment variables are read once, when the container starts. They never change for the life of the container, so changing the ConfigMap has no effect on a running Pod (see Update propagation).

Volumes: a directory of files

Mounting the ConfigMap as a volume turns each key into a file. This is the right form for real configuration files.

      containers:
        - name: catalog-api
          image: ghcr.io/example/bookshelf-catalog-api:1.0.0
          volumeMounts:
            - name: config
              mountPath: /etc/catalog          # whole-directory mount
              readOnly: true
            - name: config
              mountPath: /app/application.properties   # one file
              subPath: application.properties
      volumes:
        - name: config
          configMap:
            name: catalog-config
            items:                              # optional: choose keys and file names
              - key: application.properties
                path: application.properties
            defaultMode: 0444

Three variants, and the trade-offs between them:

  • Whole directory (no items, no subPath): every key becomes a file under mountPath, and the mount hides whatever the image had in that directory. Files are projected through a symlink swap, so they update in place.

  • items: only the listed keys are projected, with chosen file names and optional per-file mode. Use it to avoid exposing unrelated keys and to rename keys that are not valid file names in the target program.

  • subPath: mounts a single file into an existing directory without hiding its siblings — the classic way to drop one config file next to files shipped in the image. The price is that a subPath mount is a bind mount of the file at start-up, so it never receives updates.

The sampler’s Parse chapter takes the simpler route for a Deployment plus Service: the Parse server reads its MongoDB connection settings from environment variables (here populated from a ConfigMap with envFrom), which is the pattern to prefer when an application accepts configuration through its environment.

The sampler’s Ghost chapter mounts the ConfigMap over /ghost/config and works around the hidden directory by copying the file into place from the ConfigMap mount with a container command. That workaround is superseded: subPath (one file beside the image’s own files) or items (only the keys you want) do the job declaratively, and without a cp step that has to be kept in sync with the image.

Multi-file ConfigMaps for init scripts

The same sampler’s Redis chapter keeps several files in one ConfigMap — a primary and a replica configuration, an init script and a sentinel configuration — and mounts them into two containers of a StatefulSet. The pattern is still current: one ConfigMap holding related files, mounted once per container that needs them, with the script marked executable through defaultMode or per-item mode.

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
  namespace: bookshelf
data:
  master.conf: |
    maxmemory 64mb
    maxmemory-policy allkeys-lru
  replica.conf: |
    replicaof redis-0.redis 6379
  init.sh: |
    #!/bin/sh
    # pick a role from the Pod ordinal, then copy the matching file into the data volume
    case "${HOSTNAME##*-}" in
      0) cp /config/master.conf /data/redis.conf ;;
      *) cp /config/replica.conf /data/redis.conf ;;
    esac

The StatefulSet page shows this ConfigMap in context (StatefulSets).

Update propagation

Editing a ConfigMap does not restart anything. What happens next depends on how it was consumed:

Consumption Behaviour after the ConfigMap changes

env / envFrom

Never updated while the container runs; needs a new container.

Volume (whole directory or items)

Updated eventually: the kubelet re-syncs on its sync period plus its cache TTL, so expect up to about a minute. The application must re-read the files (or watch them) to notice.

Volume with subPath

Never updated; needs a new container.

Immutable ConfigMap

Cannot change at all; replace it with a new name.

For workloads that cannot hot-reload, the usual goal is "roll the Pods when the configuration changes". Three approaches:

  1. Manual restart: kubectl rollout restart deployment/catalog-api -n bookshelf creates a new ReplicaSet generation (see ReplicaSets and Deployments).

  2. Checksum annotation: put a hash of the rendered ConfigMap in the Pod template, so any change alters the template and triggers a rolling update. Helm charts conventionally use checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}.

  3. Name-suffix hash: Kustomize’s configMapGenerator appends a content hash to the ConfigMap name and rewrites every reference, so changed content means a new name, which changes the Pod template.

# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
  - name: catalog-config
    literals:
      - LOG_LEVEL=info
    files:
      - application.properties
resources:
  - deployment.yaml

The official tutorial Updating configuration via a ConfigMap walks through the volume and environment cases side by side, including observing the delay before a mounted file changes.

Applications that do hot-reload can do it themselves: Spring Cloud Kubernetes watches the API for ConfigMap changes, see Kubernetes ConfigMaps and Secrets in the Spring Boot section.

Immutable ConfigMaps and the size limit

Setting immutable: true makes the data and binaryData fields read-only for the object’s lifetime. Two benefits: accidental edits cannot break every Pod that reads the object, and the kubelet stops watching it, which reduces API server load on clusters with many ConfigMaps. The only way to change it is to delete and recreate it, which in practice means versioned names such as catalog-config-v2 with the Deployment updated to match.

apiVersion: v1
kind: ConfigMap
metadata:
  name: catalog-config-v2
  namespace: bookshelf
immutable: true
data:
  LOG_LEVEL: debug

A ConfigMap, like a Secret, is capped at 1 MiB total, because it is stored in etcd. Large files (datasets, binaries, model weights) belong in a volume, an object store or an image volume (see Volumes and Ephemeral Storage), not in a ConfigMap.

Pitfalls

  • Secrets in ConfigMaps: ConfigMaps are not encrypted at rest by default and are readable by anyone with get on them; credentials go in Secrets.

  • Numbers and booleans in data must be quoted strings, or the API server rejects the manifest.

  • Namespace boundary: a Pod cannot reference a ConfigMap from another namespace.

  • Order of precedence: when env and envFrom define the same name, env wins; among several envFrom sources, the last wins.