Configuration should be separated from code — a core 12-factor principle. In Kubernetes, ConfigMaps hold non-sensitive configuration data (settings, feature flags, config files) and inject it into Pods as environment variables or mounted files.

1. Creating ConfigMaps

From Literal Values

# Imperative:
kubectl create configmap app-config \
  --from-literal=DB_HOST=postgres.default.svc \
  --from-literal=DB_PORT=5432 \
  --from-literal=LOG_LEVEL=info

From Files

# From a single file (key = filename, value = file content):
kubectl create configmap nginx-conf --from-file=nginx.conf

# From a file with a custom key:
kubectl create configmap nginx-conf --from-file=custom-key=nginx.conf

# From a directory (each file becomes a key):
kubectl create configmap app-configs --from-file=./config/

From Environment File

# .env file:
# DB_HOST=postgres
# DB_PORT=5432
kubectl create configmap app-config --from-env-file=.env

Declarative YAML

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:                          # ← string key-value pairs
  DB_HOST: postgres.default.svc
  DB_PORT: "5432"              # ← all values are strings
  LOG_LEVEL: info
  app.properties: |            # ← multi-line config file as a value
    server.port=8080
    server.timeout=30s
    feature.dark-mode=true
binaryData:                    # ← for non-UTF8 data (base64 encoded)
  logo.png: iVBORw0KGgo...

Key Rules

RuleDetail
Values are always stringsEven numbers: "5432" not 5432
Max size1 MiB total (etcd limit)
NamespacedConfigMap must be in same namespace as Pod
Not encryptedStored as plain text in etcd — use Secrets for sensitive data
For exams, the fastest way: kubectl create configmap name --from-literal=key=value --dry-run=client -o yaml > cm.yaml. Edit if needed, then apply.

2. Consuming ConfigMaps in Pods

Method 1: Individual Environment Variables

spec:
  containers:
    - name: app
      image: myapp:latest
      env:
        - name: DATABASE_HOST        # ← env var name in container
          valueFrom:
            configMapKeyRef:
              name: app-config       # ← ConfigMap name
              key: DB_HOST           # ← key within ConfigMap
        - name: DATABASE_PORT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DB_PORT

Method 2: All Keys as Environment Variables

spec:
  containers:
    - name: app
      image: myapp:latest
      envFrom:
        - configMapRef:
            name: app-config         # All keys become env vars
          prefix: APP_               # Optional: prefix each var
# Result: APP_DB_HOST, APP_DB_PORT, APP_LOG_LEVEL

Method 3: Volume Mount (Config Files)

spec:
  containers:
    - name: nginx
      image: nginx:1.25
      volumeMounts:
        - name: config-vol
          mountPath: /etc/nginx/conf.d   # Directory where files appear
          readOnly: true
  volumes:
    - name: config-vol
      configMap:
        name: nginx-conf
        # Each key becomes a file in the mount directory
        # Key: "default.conf" → File: /etc/nginx/conf.d/default.conf

Method 4: Mount Specific Keys

  volumes:
    - name: config-vol
      configMap:
        name: app-config
        items:                        # Only mount these keys
          - key: app.properties
            path: application.properties   # custom filename
        # Result: /mountPath/application.properties

Comparison

MethodHot Reload?Best For
Env vars (valueFrom)❌ No (set at Pod start)Simple key-value settings
Env vars (envFrom)❌ NoBulk injection of many keys
Volume mount✅ Yes (~60s delay)Config files that apps read from disk
Volume mount + subPath❌ NoMount single file without hiding directory
Volume-mounted ConfigMaps auto-update! When you update a ConfigMap, kubelet syncs the mounted files within ~60 seconds (the kubelet sync period). The app must watch for file changes or re-read on signal. Environment variables are fixed at container start — they never update without a Pod restart.

3. subPath — Mount a Single File

A normal volume mount replaces the entire directory. If you mount to /etc/nginx/, all existing files in that directory disappear. Use subPath to mount a single file without hiding the directory:

spec:
  containers:
    - name: nginx
      volumeMounts:
        - name: config-vol
          mountPath: /etc/nginx/nginx.conf   # specific file path
          subPath: nginx.conf                # key from ConfigMap
  volumes:
    - name: config-vol
      configMap:
        name: nginx-conf
subPath trade-off: It preserves existing files in the directory (only adds/overwrites the one file), BUT it disables auto-update. Changes to the ConfigMap won't be reflected until the Pod is restarted. Use subPath only when you can't let the mount replace the entire directory.

4. Immutable ConfigMaps

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v2
immutable: true                # ← cannot be modified after creation
data:
  DB_HOST: postgres.default.svc
  LOG_LEVEL: debug

Why Make a ConfigMap Immutable?

BenefitExplanation
Performancekubelet stops watching for changes → reduces API server load significantly at scale
SafetyPrevents accidental edits that could break running Pods
GitOps-friendlyVersion in the name (app-config-v2), point Deployment to new name, rollback = revert name
At scale (thousands of Pods), each mounted ConfigMap creates a watch on the API server. Immutable ConfigMaps eliminate these watches entirely. Google's internal system (Borg) only has immutable config — K8s adopted this feature from that experience. In large clusters, making ConfigMaps immutable can reduce API server CPU by 30-40%.

5. ConfigMap Update Patterns

Pattern 1: Volume Mount (Auto-Sync)

# Update ConfigMap:
kubectl edit configmap app-config
# Wait ~60s → file in container updates automatically
# App must watch for file changes (inotify, SIGHUP, periodic re-read)

Pattern 2: Rollout Restart

# Update ConfigMap, then force Pod restart:
kubectl edit configmap app-config
kubectl rollout restart deployment/web
# New Pods pick up the new config at startup

Pattern 3: Config Hash Annotation (GitOps)

# In the Deployment template:
spec:
  template:
    metadata:
      annotations:
        configHash: "sha256:a1b2c3..."   # CI/CD computes this
# When ConfigMap changes → hash changes → template changes → rollout triggers
# Fully declarative, no manual restart needed

Pattern 4: Immutable + Name Versioning

# Create new ConfigMap with new name:
kubectl create configmap app-config-v3 --from-file=...

# Update Deployment to reference new name:
# volumes.configMap.name: app-config-v3
# Triggers rolling update. Rollback = point back to v2.
Pattern 4 is the safest for production. Each config version is a separate object. Rollback is just changing the reference back. Old configs are preserved for audit. Combined with immutable: true, it gives you full version control over configuration.

Summary

ConceptKey Point
ConfigMapNon-sensitive config as key-value pairs or files
Creation--from-literal, --from-file, --from-env-file, or YAML
Env varsvalueFrom.configMapKeyRef or envFrom — fixed at start
Volume mountKeys → files — auto-updates in ~60s
subPathMount single file without hiding directory — disables auto-update
ImmutableSet immutable: true — reduces API server load, prevents edits
Size limit1 MiB total per ConfigMap
Best practiceImmutable + name versioning + Deployment reference update

📝 Quiz: ConfigMaps

Q1: You mount a ConfigMap as a volume at /etc/app/. The directory already has files. What happens to them?

They are hidden (shadowed). The volume mount replaces the entire directory content with the ConfigMap keys as files. Existing files are not accessible. Use subPath if you need to preserve existing files.

Q2: You update a ConfigMap. Pods use it via env.valueFrom.configMapKeyRef. When do they see the new value?

Never (without a restart). Environment variables are set at container start and never change during the container's lifetime. You must restart the Pod (e.g., kubectl rollout restart deployment/web) for it to pick up the new value.

Q3: You mount a ConfigMap as a volume with subPath. You update the ConfigMap. Does the file in the container update?

No. subPath mounts are not auto-updated. This is a known limitation. The file remains at its original content until the Pod is restarted. For auto-updates, mount the entire ConfigMap as a directory (without subPath).

Q4: Why would you set immutable: true on a ConfigMap in a large cluster?

Performance. Each mounted ConfigMap creates a watch on the API server. With thousands of Pods, these watches consume significant API server resources. Immutable ConfigMaps eliminate the watch entirely (kubelet knows it can never change). Secondary benefit: prevents accidental edits.

Q5: A ConfigMap has keys DB_HOST, DB_PORT, and app.conf. You use envFrom to inject all keys. What environment variables does the container get?

DB_HOST and DB_PORT become env vars. app.conf is skipped because it's not a valid environment variable name (contains a dot). Keys that aren't valid env var names are silently ignored by envFrom. No error is raised.

Q6: How do you trigger a Deployment rollout when a ConfigMap changes, without using kubectl rollout restart?

Add a hash of the ConfigMap content as an annotation in the Pod template:
annotations:
configHash: "sha256:abc123"

When CI/CD updates the ConfigMap and recomputes the hash, the annotation change modifies the Pod template, triggering a rolling update automatically.