Skip to content

Module 4.1: ConfigMaps

Hands-On Lab Available
K8s Cluster intermediate 30 min
Launch Lab ↗

Opens in Killercoda in a new tab

Complexity: [MEDIUM] - Multiple ways to create and consume

Time to Complete: 40-50 minutes

Prerequisites: Module 1.1 (Pods), understanding of environment variables


After completing this module, you will be able to:

  • Create ConfigMaps from literals, files, and directories using imperative and declarative methods
  • Configure pods to consume ConfigMaps as environment variables and volume mounts
  • Debug application configuration issues caused by missing or incorrectly mounted ConfigMaps
  • Explain when to use environment variables vs volume mounts for configuration injection

ConfigMaps decouple configuration from container images. Instead of baking settings into your image, you inject them at runtime. This lets you use the same image across environments (dev, staging, production) with different configurations.

The CKAD exam frequently tests ConfigMaps because they’re fundamental to the twelve-factor app methodology. Expect questions on:

  • Creating ConfigMaps from literals, files, and directories
  • Consuming as environment variables
  • Mounting as volumes
  • Updating configurations

The Restaurant Menu Analogy

Think of ConfigMaps as a restaurant’s specials board. The kitchen (container image) stays the same, but the specials (configuration) change daily. The chef doesn’t rebuild the kitchen to change the menu—they just update the board. ConfigMaps work the same way: change the config, restart the pod, get new behavior.


Terminal window
# Single key-value
k create configmap app-config --from-literal=APP_ENV=production
# Multiple key-values
k create configmap app-config \
--from-literal=APP_ENV=production \
--from-literal=LOG_LEVEL=info \
--from-literal=MAX_CONNECTIONS=100
# View the result
k get configmap app-config -o yaml
Terminal window
# Create a config file
echo "database.host=db.example.com
database.port=5432
database.name=myapp" > app.properties
# Create ConfigMap from file
k create configmap app-config --from-file=app.properties
# Custom key name
k create configmap app-config --from-file=config.properties=app.properties
# Multiple files
k create configmap app-config \
--from-file=app.properties \
--from-file=logging.properties
Terminal window
# All files in directory become keys
k create configmap app-config --from-file=./config-dir/
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: production
LOG_LEVEL: info
app.properties: |
database.host=db.example.com
database.port=5432
database.name=myapp

Single variable:

apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx
env:
- name: APP_ENVIRONMENT
valueFrom:
configMapKeyRef:
name: app-config
key: APP_ENV

All keys as variables:

apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx
envFrom:
- configMapRef:
name: app-config

Pause and predict: You update a ConfigMap that a pod consumes via envFrom. Will the pod automatically pick up the new values? What if it was mounted as a volume instead?

Mount entire ConfigMap:

apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: app-config

Mount specific keys:

apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: app-config
items:
- key: app.properties
path: application.properties

Stop and think: When you mount a ConfigMap as a volume at /etc/config, what happens to any files that were already in /etc/config inside the container image? How would you avoid this problem?

Mount to specific file:

volumeMounts:
- name: config-volume
mountPath: /etc/config/app.conf
subPath: app.properties

Terminal window
# Development
k create configmap app-config \
--from-literal=APP_ENV=development \
--from-literal=DEBUG=true \
-n development
# Production
k create configmap app-config \
--from-literal=APP_ENV=production \
--from-literal=DEBUG=false \
-n production
Terminal window
# nginx.conf
cat << 'EOF' > nginx.conf
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
}
}
EOF
k create configmap nginx-config --from-file=nginx.conf
# Mount in pod
cat << 'EOF' | k apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx-custom
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d/default.conf
subPath: nginx.conf
volumes:
- name: config
configMap:
name: nginx-config
EOF

MethodUpdate Behavior
Environment variablesNOT updated - requires pod restart
Volume mountsUpdated automatically (kubelet sync period ~1 min)
subPath mountsNOT updated - requires pod restart
Terminal window
# Restart pods to pick up env var changes
k rollout restart deployment/myapp
# For volume-mounted configs, wait or force sync
# Pods auto-update within kubelet sync period

┌─────────────────────────────────────────────────────────────┐
│ ConfigMap Usage │
├─────────────────────────────────────────────────────────────┤
│ │
│ ConfigMap: app-config │
│ ┌─────────────────────────────────────┐ │
│ │ APP_ENV: production │ │
│ │ LOG_LEVEL: info │ │
│ │ app.properties: | │ │
│ │ database.host=db.example.com │ │
│ │ database.port=5432 │ │
│ └─────────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ envFrom │ │ volume │ │
│ │ │ │ mount │ │
│ │ $APP_ENV │ │ │ │
│ │ $LOG_LEVEL │ │ /etc/config/ │ │
│ └──────────────┘ │ app.properties│ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Terminal window
# Create
k create configmap NAME --from-literal=KEY=VALUE
k create configmap NAME --from-file=FILE
k create configmap NAME --from-file=DIR/
# View
k get configmap NAME -o yaml
k describe configmap NAME
# Edit
k edit configmap NAME
# Delete
k delete configmap NAME

  • ConfigMaps have a 1MB size limit. For larger configurations, consider mounting external storage or using init containers.

  • ConfigMap data is stored in etcd unencrypted (unlike Secrets which can be encrypted at rest). Don’t put sensitive data in ConfigMaps.

  • The immutable: true field (Kubernetes 1.21+) prevents accidental changes and improves cluster performance by reducing watch load.


From the Trenches: The “Masked Directory” Outage

Section titled “From the Trenches: The “Masked Directory” Outage”

A platform team was deploying a new logging agent DaemonSet across a production cluster. The agent required a custom configuration file, which the team provided via a ConfigMap. To inject it, they mounted the ConfigMap volume directly to /etc/agent/ where the agent looked for its configuration.

As soon as the DaemonSet was applied, the logging agent crashed cluster-wide. The logs showed FATAL: Missing required default certificates.

The post-mortem revealed the root cause: The base container image included critical default files (like internal certificates and base configuration) baked into the /etc/agent/ directory. By mounting the ConfigMap volume directly to the directory path (mountPath: /etc/agent/), Kubernetes effectively “masked” all the existing contents of that directory with the contents of the ConfigMap. The agent couldn’t find its certificates and crashed. The team fixed the outage by using a subPath mount (mountPath: /etc/agent/custom.conf with subPath: custom.conf), which injects only the specific file into the directory without hiding the existing baked-in files.


MistakeWhy It HurtsSolution
Expecting env vars to updateApp uses stale configRestart pod after ConfigMap changes
Using subPath expecting updatessubPath doesn’t auto-updateUse full volume mount or restart
Storing secrets in ConfigMapsData visible in plain textUse Secrets for sensitive data
Not namespacing ConfigMapsConfig leaks across environmentsCreate per-namespace ConfigMaps
Typo in key namePod won’t start or gets wrong configUse k describe cm to verify

  1. A developer updates a ConfigMap with new database connection settings. They confirm the ConfigMap is correct with kubectl get cm -o yaml. But their pod still uses the old values. The pod consumes the ConfigMap via envFrom. What is happening and how do they fix it?

    Answer Environment variables from ConfigMaps are injected at pod startup time and are never updated afterward, even if the underlying ConfigMap changes. The developer must restart the pods to pick up the new values — for a Deployment, `kubectl rollout restart deployment/name` is the cleanest approach. If the team needs live config reloading, they should switch to volume-mounted ConfigMaps, which the kubelet automatically syncs within ~1 minute. However, the application must also be written to watch for file changes and reload.
  2. You mount a ConfigMap as a volume at /etc/nginx/conf.d/. After the pod starts, nginx fails because it can’t find its default configuration files. Before adding the ConfigMap mount, the directory had default.conf provided by the nginx image. What went wrong?

    Answer Mounting a ConfigMap (or any volume) at a directory path completely replaces the directory's contents. The original files from the container image at `/etc/nginx/conf.d/` are hidden by the mount. The fix is to use `subPath` to mount a specific file instead of the entire directory: `mountPath: /etc/nginx/conf.d/my-custom.conf` with `subPath: my-custom.conf`. This preserves the existing files in the directory. The trade-off is that subPath mounts do not auto-update when the ConfigMap changes.
  3. A team stores their database password in a ConfigMap alongside other non-sensitive application settings. During a security audit, this is flagged. Why is this a problem, and what should they do differently?

    Answer ConfigMap data is stored as plain text in etcd and is visible to anyone who can run `kubectl get cm -o yaml`. It also appears unobfuscated in `kubectl describe`, pod environment listings, and API responses. Sensitive data like passwords should be stored in Secrets, which provide base64 encoding (not encryption, but not human-readable at a glance), can be encrypted at rest with EncryptionConfiguration, and have stricter RBAC controls. Move the password to a Secret and reference it via `secretKeyRef` in the pod spec, keeping only non-sensitive settings in the ConfigMap.
  4. You create a ConfigMap from a file: kubectl create configmap app-config --from-file=settings.conf. When you mount it as a volume, you expect a file called settings.conf at the mount path. Instead, the application can’t find it. You check and the file is there but the application config parser is looking for app.conf. How do you make the ConfigMap mount use a different filename?

    Answer Use the `items` field in the volume definition to remap the key to a different file path: `volumes: - name: config, configMap: name: app-config, items: - key: settings.conf, path: app.conf`. This tells Kubernetes to take the ConfigMap key `settings.conf` and project it as a file named `app.conf` in the mounted volume directory. Alternatively, you could create the ConfigMap with a custom key name from the start: `kubectl create configmap app-config --from-file=app.conf=settings.conf`. Either approach solves the file path discrepancy at the infrastructure level rather than requiring an application code change.

Task: Create and consume ConfigMaps multiple ways.

Setup:

Terminal window
# Create ConfigMap from literals
k create configmap web-config \
--from-literal=APP_COLOR=blue \
--from-literal=APP_MODE=production
# Verify
k get configmap web-config -o yaml

Part 1: Environment Variables

Terminal window
cat << 'EOF' | k apply -f -
apiVersion: v1
kind: Pod
metadata:
name: env-pod
spec:
containers:
- name: app
image: busybox
command: ['sh', '-c', 'echo Color: $APP_COLOR, Mode: $APP_MODE && sleep 3600']
envFrom:
- configMapRef:
name: web-config
EOF
# Verify environment
k logs env-pod

Part 2: Volume Mount

Terminal window
# Create config file
k create configmap nginx-index --from-literal=index.html='<h1>Hello from ConfigMap</h1>'
cat << 'EOF' | k apply -f -
apiVersion: v1
kind: Pod
metadata:
name: vol-pod
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: nginx-index
EOF
# Test
k exec vol-pod -- cat /usr/share/nginx/html/index.html

Cleanup:

Terminal window
k delete pod env-pod vol-pod
k delete configmap web-config nginx-index

Drill 1: Create from Literals (Target: 1 minute)

Section titled “Drill 1: Create from Literals (Target: 1 minute)”
Terminal window
k create configmap drill1 --from-literal=KEY1=value1 --from-literal=KEY2=value2
k get cm drill1 -o yaml
k delete cm drill1

Drill 2: Create from File (Target: 2 minutes)

Section titled “Drill 2: Create from File (Target: 2 minutes)”
Terminal window
echo "setting1=on
setting2=off" > /tmp/settings.conf
k create configmap drill2 --from-file=/tmp/settings.conf
k get cm drill2 -o yaml
k delete cm drill2

Drill 3: Environment Variables (Target: 3 minutes)

Section titled “Drill 3: Environment Variables (Target: 3 minutes)”
Terminal window
k create configmap drill3 --from-literal=DB_HOST=localhost --from-literal=DB_PORT=5432
cat << 'EOF' | k apply -f -
apiVersion: v1
kind: Pod
metadata:
name: drill3
spec:
containers:
- name: app
image: busybox
command: ['sh', '-c', 'env | grep DB && sleep 3600']
envFrom:
- configMapRef:
name: drill3
EOF
k logs drill3
k delete pod drill3 cm drill3
Terminal window
k create configmap drill4 --from-literal=config.json='{"debug": true}'
cat << 'EOF' | k apply -f -
apiVersion: v1
kind: Pod
metadata:
name: drill4
spec:
containers:
- name: app
image: busybox
command: ['sh', '-c', 'cat /config/config.json && sleep 3600']
volumeMounts:
- name: cfg
mountPath: /config
volumes:
- name: cfg
configMap:
name: drill4
EOF
k logs drill4
k delete pod drill4 cm drill4

Drill 5: Specific Key Mount (Target: 3 minutes)

Section titled “Drill 5: Specific Key Mount (Target: 3 minutes)”
Terminal window
k create configmap drill5 \
--from-literal=app.conf='main config' \
--from-literal=log.conf='log config'
cat << 'EOF' | k apply -f -
apiVersion: v1
kind: Pod
metadata:
name: drill5
spec:
containers:
- name: app
image: busybox
command: ['sh', '-c', 'ls /config && cat /config/application.conf && sleep 3600']
volumeMounts:
- name: cfg
mountPath: /config
volumes:
- name: cfg
configMap:
name: drill5
items:
- key: app.conf
path: application.conf
EOF
k logs drill5
k delete pod drill5 cm drill5

Drill 6: Complete Scenario (Target: 5 minutes)

Section titled “Drill 6: Complete Scenario (Target: 5 minutes)”

Scenario: Deploy nginx with custom configuration.

Terminal window
# Create nginx config
cat << 'NGINX' > /tmp/nginx.conf
server {
listen 8080;
location / {
return 200 'Custom Config Works!\n';
add_header Content-Type text/plain;
}
}
NGINX
k create configmap drill6-nginx --from-file=/tmp/nginx.conf
cat << 'EOF' | k apply -f -
apiVersion: v1
kind: Pod
metadata:
name: drill6
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 8080
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: nginx.conf
volumes:
- name: nginx-config
configMap:
name: drill6-nginx
EOF
# Test (wait for pod ready)
k wait --for=condition=Ready pod/drill6 --timeout=30s
k exec drill6 -- curl -s localhost:8080
k delete pod drill6 cm drill6-nginx

Module 4.2: Secrets - Manage sensitive data securely.