ConfigMap
Learn what Kubernetes ConfigMap is, why it is used, how it works, where it is used, and how to troubleshoot ConfigMap-related pod issues with real-time examples.
Table of Contents
- What is a ConfigMap?
- Why is ConfigMap Used?
- How ConfigMap Works
- Where ConfigMap is Used
- ConfigMap Structure and Syntax
- Ways to Consume a ConfigMap in a Pod
- Real-World Example: Node.js App with ConfigMap
- Troubleshooting ConfigMap Issues
- Best Practices
- Summary
What is a ConfigMap?
A ConfigMap is a Kubernetes API object used to store non-confidential configuration data as key-value pairs. It decouples environment-specific configuration from your container images, making applications more portable and easier to manage across different environments (development, staging, production).
Think of a ConfigMap as a configuration file stored inside Kubernetes — instead of baking configuration values (like database hostnames, feature flags, log levels, or API endpoints) directly into your Docker image or hardcoding them in your app code, you store them in a ConfigMap and inject them into your pods at runtime.
Note: ConfigMaps are not designed to store sensitive data such as passwords, tokens, or keys. For sensitive data, use Kubernetes Secrets.
Why is ConfigMap Used?
Before ConfigMaps existed, developers faced a common problem: the same Docker image had to be rebuilt every time a configuration value changed for a different environment. This violated the “build once, deploy anywhere” principle.
ConfigMaps solve this by enabling:
1. Environment Separation
The same container image can run in dev, staging, and prod with different configurations injected at deploy time — no image rebuilds needed.
2. Cleaner Container Images Your Dockerfile stays lean. Configuration is managed externally by the Kubernetes platform team, not embedded in images.
3. Dynamic Configuration Updates When a ConfigMap is mounted as a volume, Kubernetes can propagate updates to running pods without requiring a pod restart (with some caveats — see below).
4. Separation of Concerns Developers own the application code; platform/ops teams own environment-specific configuration. ConfigMaps enforce this separation at the infrastructure level.
5. GitOps-Friendly ConfigMap YAML files can be committed to Git, reviewed in pull requests, and deployed via CI/CD pipelines alongside your application manifests.
How ConfigMap Works
At a high level, ConfigMap works in three steps:
1. You CREATE a ConfigMap (YAML manifest or kubectl command)
↓
2. You REFERENCE the ConfigMap in your Pod spec
↓
3. Kubernetes INJECTS the data into the Pod as environment variables or mounted files
Internal Architecture
Kubernetes stores ConfigMap data in etcd (the cluster’s distributed key-value store). When a pod is scheduled and created, the kubelet on the worker node reads the pod spec, identifies any referenced ConfigMaps, fetches the data, and either:
- Sets environment variables in the container process, or
- Writes the data to files in a mounted volume inside the container.
ConfigMap Data Limits
- Maximum size of a ConfigMap is 1 MiB (1,048,576 bytes).
- For larger configuration files, consider mounting a PersistentVolume or using an external config server (like Vault or AWS AppConfig).
Where ConfigMap is Used
ConfigMaps are used across virtually every Kubernetes workload. Common real-world use cases include:
| Use Case | Example |
|---|---|
| Application settings | LOG_LEVEL=debug, MAX_RETRIES=3 |
| Database connection config | DB_HOST=postgres-service, DB_PORT=5432 |
| Feature flags | FEATURE_DARK_MODE=true |
| External API endpoints | PAYMENT_API_URL=https://api.stripe.com |
| Nginx/Apache config files | Full nginx.conf mounted as a volume |
Spring Boot application.properties | Entire properties file injected into a Java app |
| Shell scripts | Startup scripts injected into init containers |
| Prometheus scrape config | Prometheus configuration file |
ConfigMap Structure and Syntax
Basic Structure
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
namespace: default
labels:
app: my-app
env: production
data:
# Simple key-value pairs
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
APP_PORT: "8080"
DATABASE_HOST: "postgres-service.default.svc.cluster.local"
DATABASE_PORT: "5432"
# Multi-line value (e.g., a config file)
app.properties: |
server.port=8080
spring.datasource.url=jdbc:postgresql://postgres:5432/mydb
spring.datasource.username=appuser
logging.level.root=INFO
# Nginx config as a value
nginx.conf: |
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend-service:3000;
}
}
Creating a ConfigMap via kubectl
From literals (quick testing):
kubectl create configmap my-app-config \
--from-literal=LOG_LEVEL=info \
--from-literal=APP_PORT=8080 \
--from-literal=DATABASE_HOST=postgres-service
From a file:
# Creates a key named "app.properties" with the file contents as the value
kubectl create configmap my-app-config --from-file=app.properties
From a directory (all files in the directory become keys):
kubectl create configmap my-app-config --from-file=./config-dir/
From a YAML manifest:
kubectl apply -f configmap.yaml
Verify the ConfigMap was created:
kubectl get configmap my-app-config -o yaml
Ways to Consume a ConfigMap in a Pod
There are three primary ways to consume ConfigMap data inside a pod.
Method 1: Environment Variables (Individual Keys)
Inject specific keys from a ConfigMap as individual environment variables.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:1.0
env:
- name: LOG_LEVEL # env var name inside the container
valueFrom:
configMapKeyRef:
name: my-app-config # ConfigMap name
key: LOG_LEVEL # key inside the ConfigMap
- name: APP_PORT
valueFrom:
configMapKeyRef:
name: my-app-config
key: APP_PORT
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: my-app-config
key: DATABASE_HOST
Inside the container, your app reads these as standard OS environment variables:
echo $LOG_LEVEL # → info
echo $APP_PORT # → 8080
echo $DATABASE_HOST # → postgres-service.default.svc.cluster.local
Method 2: envFrom (All Keys at Once)
Load all key-value pairs from a ConfigMap as environment variables in one shot.
spec:
containers:
- name: my-app
image: my-app:1.0
envFrom:
- configMapRef:
name: my-app-config # All keys become env vars automatically
Pros: Simple — no need to list every key individually. Cons: All keys are exposed. If a key has an invalid env var name (e.g., contains hyphens or dots), it is silently skipped.
Method 3: Volume Mount (Files)
Mount ConfigMap data as files inside the container’s filesystem. This is ideal for full configuration files like nginx.conf, application.properties, or .env files.
spec:
containers:
- name: my-app
image: my-app:1.0
volumeMounts:
- name: config-volume
mountPath: /etc/config # Directory inside container
readOnly: true
volumes:
- name: config-volume
configMap:
name: my-app-config # Each key becomes a file
With the ConfigMap defined earlier, this creates the following inside the container:
/etc/config/
├── LOG_LEVEL → contains: "info"
├── APP_PORT → contains: "8080"
├── DATABASE_HOST → contains: "postgres-service..."
├── app.properties → contains the full properties file
└── nginx.conf → contains the full nginx config
Mounting a specific key as a specific file path:
volumes:
- name: config-volume
configMap:
name: my-app-config
items:
- key: nginx.conf
path: nginx.conf # file will be at /etc/nginx/nginx.conf
Real-World Example: Node.js App with ConfigMap
Let’s walk through a complete, realistic example of deploying a Node.js Express API that reads its configuration from a ConfigMap.
Scenario
We have a Node.js app that connects to a PostgreSQL database. We want to manage the database host, port, database name, app port, and log level through a ConfigMap.
Step 1: Create the ConfigMap
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-app-config
namespace: default
data:
APP_PORT: "3000"
LOG_LEVEL: "info"
NODE_ENV: "production"
DB_HOST: "postgres-service"
DB_PORT: "5432"
DB_NAME: "myappdb"
DB_MAX_CONNECTIONS: "10"
Apply it:
kubectl apply -f configmap.yaml
# Output: configmap/nodejs-app-config created
Step 2: Create the Deployment
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-api
namespace: default
labels:
app: nodejs-api
spec:
replicas: 2
selector:
matchLabels:
app: nodejs-api
template:
metadata:
labels:
app: nodejs-api
spec:
containers:
- name: nodejs-api
image: myrepo/nodejs-api:1.2.0
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: nodejs-app-config
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
Apply it:
kubectl apply -f deployment.yaml
# Output: deployment.apps/nodejs-api created
Step 3: Verify the Pod Reads ConfigMap Values
Check that the pods are running:
kubectl get pods -l app=nodejs-api
# NAME READY STATUS RESTARTS AGE
# nodejs-api-7d9f6c8b4d-k2xlt 1/1 Running 0 45s
# nodejs-api-7d9f6c8b4d-p8nqz 1/1 Running 0 45s
Exec into a pod and verify the environment variables:
kubectl exec -it nodejs-api-7d9f6c8b4d-k2xlt -- env | grep -E "APP_PORT|LOG_LEVEL|DB_HOST"
# APP_PORT=3000
# LOG_LEVEL=info
# DB_HOST=postgres-service
Step 4: Update the ConfigMap
Suppose we want to change the log level to debug for troubleshooting:
kubectl edit configmap nodejs-app-config
# Change LOG_LEVEL from "info" to "debug" in the editor, then save
Or via patch:
kubectl patch configmap nodejs-app-config \
--type merge \
-p '{"data": {"LOG_LEVEL": "debug"}}'
Important: Pods using envFrom or env.valueFrom do NOT automatically pick up ConfigMap updates. You need to restart the deployment:
kubectl rollout restart deployment/nodejs-api
# Output: deployment.apps/nodejs-api restarted
Pods using volume mounts will eventually receive updated file contents (typically within 60 seconds), but the application must re-read the file to pick up changes.
Troubleshooting ConfigMap Issues
Here are the most common ConfigMap-related problems and how to diagnose and fix them in real environments.
Problem 1: Pod Stuck in CreateContainerConfigError
Symptom:
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# nodejs-api-7d9f6c8b4d-k2xlt 0/1 CreateContainerConfigError 0 10s
Diagnosis:
kubectl describe pod nodejs-api-7d9f6c8b4d-k2xlt
Look for an Events section like:
Events:
Warning Failed 5s kubelet Error: configmap "nodejs-app-config" not found
Root Cause: The pod references a ConfigMap that doesn’t exist yet in the same namespace.
Fix:
# Check which namespace your pod is in
kubectl get pod nodejs-api-7d9f6c8b4d-k2xlt -o jsonpath='{.metadata.namespace}'
# default
# Check if the ConfigMap exists in that namespace
kubectl get configmap -n default
# If it's missing, create it:
kubectl apply -f configmap.yaml -n default
Problem 2: Environment Variable Has Wrong or Empty Value
Symptom: Application behaves unexpectedly — wrong database host, wrong port, missing feature flag.
Diagnosis:
# Inspect the running pod's actual environment
kubectl exec -it nodejs-api-7d9f6c8b4d-k2xlt -- env | grep DB_HOST
# DB_HOST= <-- empty!
# Check the ConfigMap contents
kubectl get configmap nodejs-app-config -o yaml
Example Output (problem visible):
data:
DB_HOST: "" # ← empty value — this is the bug
DB_PORT: "5432"
Root Cause options:
- ConfigMap key has an empty value.
- Key name mismatch (typo in ConfigMap key vs pod spec reference).
- Pod is using a cached, stale ConfigMap (not yet restarted after update).
Fix:
# Edit the ConfigMap directly
kubectl edit configmap nodejs-app-config
# Fix the empty DB_HOST value, save
# Restart the deployment to pick up the change
kubectl rollout restart deployment/nodejs-api
Problem 3: Pod Cannot Find a Specific Key (configMapKeyRef)
Symptom:
kubectl describe pod nodejs-api-7d9f6c8b4d-k2xlt
Events:
Warning Failed 3s kubelet Error: couldn't find key LOG_LEVEL in ConfigMap default/nodejs-app-config
Root Cause: The pod’s env.valueFrom.configMapKeyRef.key value (LOG_LEVEL) does not exist as a key in the ConfigMap’s data section.
Diagnosis:
# List all keys in the ConfigMap
kubectl get configmap nodejs-app-config -o jsonpath='{.data}' | python3 -m json.tool
# {
# "App_Port": "3000", ← notice: wrong case!
# "log_level": "info", ← lowercase, but pod spec references "LOG_LEVEL"
# "DB_HOST": "postgres-service"
# }
Fix:
Option A — Fix the ConfigMap key to match what the pod expects:
kubectl patch configmap nodejs-app-config \
--type merge \
-p '{"data": {"LOG_LEVEL": "info"}}'
Option B — Fix the pod spec to match the actual key name (update the Deployment YAML and re-apply).
Problem 4: Volume-Mounted ConfigMap File Not Updating
Symptom: You updated a ConfigMap but the application inside the pod is still reading the old configuration file.
Diagnosis:
# Check the file inside the container
kubectl exec -it nodejs-api-7d9f6c8b4d-k2xlt -- cat /etc/config/app.properties
# Still shows old values
# Check if Kubernetes has updated the file (uses symlinks internally)
kubectl exec -it nodejs-api-7d9f6c8b4d-k2xlt -- ls -la /etc/config/
# lrwxrwxrwx app.properties -> ..data/app.properties
# The symlink updates when kubelet syncs — default sync period is 60s
Root Cause options:
- Kubelet sync period hasn’t elapsed yet (default: 60 seconds). Wait and check again.
- The application is caching the file in memory at startup and not re-reading it.
- The ConfigMap is immutable (
.immutable: true) — immutable ConfigMaps cannot be updated.
Fix:
# Check if ConfigMap is immutable
kubectl get configmap nodejs-app-config -o jsonpath='{.immutable}'
# true ← this is why updates aren't working
# Immutable ConfigMaps CANNOT be edited. You must delete and recreate:
kubectl delete configmap nodejs-app-config
kubectl apply -f configmap-updated.yaml
# Then restart the pod to remount:
kubectl rollout restart deployment/nodejs-api
Problem 5: ConfigMap Exceeds 1 MiB Size Limit
Symptom:
kubectl apply -f large-configmap.yaml
# Error from server: etcd: request is too large
Diagnosis:
# Check the size of your ConfigMap YAML
wc -c configmap.yaml
# 1200000 bytes — over 1 MiB
Fix Options:
- Break the ConfigMap into multiple smaller ConfigMaps.
- Store large files in a PersistentVolume and mount it instead.
- Use an external configuration service (HashiCorp Vault, AWS AppConfig, GCP Secret Manager).
Problem 6: ConfigMap Updates Not Reflected After kubectl rollout restart
Symptom: You updated the ConfigMap, restarted the deployment, but pods still show the old values.
Diagnosis:
# Verify the ConfigMap has the new value
kubectl get configmap nodejs-app-config -o jsonpath='{.data.LOG_LEVEL}'
# debug ← correctly updated
# Check the new pod (post-restart)
kubectl get pods -l app=nodejs-api
# Note the new pod name (age will be recent)
kubectl exec -it nodejs-api-NEW-POD-NAME -- env | grep LOG_LEVEL
# LOG_LEVEL=info ← still old value!
Root Cause: The deployment’s Pod template was not changed, so Kubernetes may have scheduled the new pod using a cached version of the ConfigMap from etcd if the pod started before the ConfigMap update fully propagated.
Fix:
# Force a full rollout by adding an annotation to the pod template
kubectl patch deployment nodejs-api \
-p '{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}}}}}'
This forces the pod template hash to change, guaranteeing new pods are created.
Quick Troubleshooting Reference
| Symptom | First Command to Run | Likely Cause |
|---|---|---|
CreateContainerConfigError | kubectl describe pod <pod> | ConfigMap does not exist |
| Empty env var | kubectl exec <pod> -- env | Empty value or key mismatch in ConfigMap |
| Key not found error | kubectl get cm <name> -o yaml | Key name typo or wrong case |
| Volume file not updated | kubectl exec <pod> -- cat <file> | Immutable CM, or app caching config |
| Size limit error | wc -c configmap.yaml | ConfigMap over 1 MiB |
| Wrong namespace | kubectl get cm -A | CM in wrong namespace |
Best Practices
1. Always use namespaces ConfigMaps are namespace-scoped. Always specify the namespace explicitly to avoid cross-environment confusion:
kubectl apply -f configmap.yaml -n production
2. Use immutable: true for stable configs
Immutable ConfigMaps improve cluster performance (kubelet doesn’t watch them for changes) and prevent accidental updates:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v1
immutable: true
data:
APP_VERSION: "1.2.0"
3. Name ConfigMaps with version suffixes for immutable configs Since immutable ConfigMaps can’t be updated, use versioned names and update the Deployment to reference the new version:
app-config-v1 → app-config-v2 → app-config-v3
4. Never store secrets in ConfigMaps
ConfigMap data is stored in plain text in etcd. Use Secret objects for passwords, tokens, certificates, and API keys.
5. Validate ConfigMaps before applying
kubectl apply -f configmap.yaml --dry-run=client
6. Label your ConfigMaps for easy management
metadata:
labels:
app: nodejs-api
env: production
version: "1.2"
7. Use envFrom for simplicity, env.valueFrom for control
envFrom: Load all keys — great for apps that read everything from env.env.valueFrom: Load specific keys — better when you need to rename keys or map only a subset.
8. Document your ConfigMap keys Add comments in the YAML to explain what each key does, especially for less obvious settings.
Summary
| Concept | Detail |
|---|---|
| What | Kubernetes object to store non-sensitive config as key-value pairs |
| Why | Decouples config from images; enables environment portability |
| How | Stored in etcd; injected into pods via env vars or volume mounts |
| Where | Used in any workload: Deployments, StatefulSets, DaemonSets, Jobs |
| Max Size | 1 MiB |
| Scope | Namespace-scoped |
| Sensitive Data | Use Secret instead |
| Auto-Updates | Volume mounts update within ~60s; env vars require pod restart |
| Immutability | Set immutable: true to prevent changes and improve performance |
ConfigMaps are a foundational Kubernetes concept. Mastering them gives you clean, portable, and manageable application configuration across all environments in your cluster.
Next: Kubernetes Secrets — Managing Sensitive Configuration Data