ConfigMap #
In modern distributed application architecture, strict separation between application logic code and configuration parameters is a must. This principle is enshrined as the third pillar of the 12-Factor App methodology, stating that all configuration must be stored in the execution environment, not in the source code (hardcoded). Kubernetes elegantly facilitates this need through an object called ConfigMap.
A ConfigMap is a Kubernetes resource specifically designed to store non-sensitive configuration data as key-value pairs. By using ConfigMaps, we can build one generic container image, then run it across different working environments — like development, staging, and production — just by injecting a different ConfigMap in each environment. This approach not only saves build time, but also guarantees release consistency because the image tested in staging is exactly the same image running in production.
ConfigMap Anatomy #
Structurally, a ConfigMap’s YAML manifest is divided into two main parts under the data spec block: data for standard textual values, and binaryData for storing binary content (like small binary files or encrypted configuration files) in Base64 format.
Let’s study the comprehensive ConfigMap manifest example below containing single key parameters and multi-line configuration files:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-gateway-config
namespace: production
data:
# Simple key-value parameters
DATABASE_TIMEOUT: "30s"
MAX_CONNECTIONS: "150"
LOG_LEVEL: "info"
APP_ENV: "production"
# A complete Nginx config file embedded as a multi-line text block
nginx.conf: |
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name api.internal.local;
location / {
proxy_pass http://localhost:8080;
}
}
}
# An internal YAML application config file
app-settings.yaml: |
cache:
provider: redis
ttl: 3600
redis:
host: redis-master.production.svc.cluster.local
port: 6379
ConfigMap Creation Methods #
Kubernetes lets us create ConfigMaps either through the imperative approach directly via the kubectl CLI or the declarative approach using YAML manifest files. For large-scale production operational needs, we must use the declarative approach because it supports infrastructure as code (IaC) principles and integrates with GitOps pipelines.
Configuration Creation Comparison #
Here’s the difference between imperatively creating configuration manually in the production terminal (anti-pattern) and declaratively creating structured configuration (solution):
# ANTI-PATTERN: Creating a ConfigMap imperatively directly on the production cluster
# This leaves no trace in Git, making disaster recovery harder
kubectl create configmap app-config \
--from-literal=DB_TIMEOUT=30s \
--from-file=nginx.conf=/tmp/nginx.conf \
-n production
# CORRECT: Create a YAML manifest declaratively and apply it using kubectl apply
# ✓ This approach tracks changes in Git and supports configuration rollback
kubectl apply -f /deployments/production/config/app-gateway-config.yaml
For quick local development needs, we can also dynamically create a ConfigMap from a directory containing a set of configuration files:
# Creating a ConfigMap from all files in the ./configs folder
kubectl create configmap app-settings-dir --from-file=./configs/ -n development
In the command above, every file name in the ./configs directory automatically becomes a key, and the entire file contents become the value in the resulting ConfigMap object.
Using ConfigMaps as Environment Variables #
One of the most common ways to consume ConfigMap data inside a Pod is injecting it as container environment variables. Kubernetes provides two main mechanisms for this: injecting specific entries using the valueFrom parameter, or loading all ConfigMap entries at once using envFrom.
Variable Injection Method Comparison #
Here’s a comparison example between inefficiently loading variables manually one by one (anti-pattern) and dynamically loading the whole configuration group (solution):
# ANTI-PATTERN: Injecting dozens of parameters manually one by one
# ✗ Makes the Pod manifest balloon and hard to maintain when new parameters are added
apiVersion: v1
kind: Pod
metadata:
name: app-pod-inefficient
spec:
containers:
- name: app
image: my-app:v1.0
env:
- name: DB_TIMEOUT
valueFrom:
configMapKeyRef:
name: app-gateway-config
key: DATABASE_TIMEOUT
- name: MAX_CONN
valueFrom:
configMapKeyRef:
name: app-gateway-config
key: MAX_CONNECTIONS
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-gateway-config
key: LOG_LEVEL
---
# CORRECT: Inject all ConfigMap keys in bulk using envFrom
# ✓ Very clean, automatically maps every ConfigMap key into an environment variable
apiVersion: v1
kind: Pod
metadata:
name: app-pod-efficient
spec:
containers:
- name: app
image: my-app:v1.0
envFrom:
- configMapRef:
name: app-gateway-config
[!WARNING] The main limitation of the environment variable method is its static nature. If we update the ConfigMap contents in etcd, the environment variables inside running containers don’t change. We must restart the container (pod restart) so the application process can read the new environment variable values.
Using ConfigMaps as Volume Mounts #
If our configuration is large, consists of a directory file structure, or needs dynamic updates without restarting Pods, the best approach is mounting the ConfigMap as physical files inside the container filesystem using the Volume Mount mechanism.
When we define a ConfigMap-based volume, Kubernetes creates a virtual directory inside the container and projects every key in the ConfigMap into a physical file.
ConfigMap Volume Mount Implementation Manifest #
Here’s a Deployment manifest example mounting specific ConfigMap keys to the application configuration directory:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-gateway
namespace: production
spec:
replicas: 2
selector:
matchLabels:
app: web-gateway
template:
metadata:
labels:
app: web-gateway
spec:
volumes:
- name: gateway-config-volume
configMap:
name: app-gateway-config
# Set the default POSIX file permission for non-root read-only access
defaultMode: 420 # Equivalent to octal 0644
items:
- key: nginx.conf
path: nginx.conf # The physical file name inside the volume
- key: app-settings.yaml
path: settings/config.yaml # Places the file into an internal sub-path
containers:
- name: nginx-proxy
image: nginx:1.25.3-alpine
volumeMounts:
- name: gateway-config-volume
mountPath: /etc/nginx/config # The mount path inside the container
readOnly: true
Directory Overwriting Risk with Volume Mounts #
When we mount a volume directly to an existing directory inside the container (e.g. /etc/nginx), all original contents from the container image in that directory get hidden and overwritten by the ConfigMap files.
If we only want to insert one configuration file into a system directory without losing other important files, we have two solution options:
- Use a New Sub-directory: Don’t mount directly to
/etc/nginx, but mount to/etc/nginx/conf.dor another custom directory read by the application. - Use the subPath Property: We can specify
subPathon thevolumeMountsparameter to project a single file without overwriting the directory. However, note that usingsubPathdisables the auto-update feature explained below.
The Auto-Update Symlink Mechanism on Volume Mounts #
The main advantage of mounting a ConfigMap as a Volume instead of injecting it as environment variables is its ability to dynamically update files (hot reload) without requiring a Pod restart.
How the Kubelet Updates Files Atomically #
When we update ConfigMap data in the API Server, the kubelet agent on every worker node detects that change in the next synchronization cycle (by default ranging from 1 to 2 minutes). The kubelet doesn’t directly rewrite files being read by containers to avoid partial-write file corruption. The kubelet applies an atomic symlink swap technique.
Let’s look at the file representation structure inside the container directory when a ConfigMap volume is mounted:
/etc/nginx/config/
├── ..2026_06_17_07_00_00.000000000/ <-- The actual version 1 directory
│ ├── nginx.conf
│ └── app-settings.yaml
├── ..data ──> ..2026_06_17_07_00_00.000000000/ <-- Symlink pointing to active data
├── nginx.conf ──> ..data/nginx.conf <-- File symlink to the actual file
└── app-settings.yaml ──> ..data/app-settings.yaml
When we make ConfigMap data changes, the kubelet synchronization process runs as follows:
Step 1: The kubelet creates a new timestamp directory (version 2)
/etc/nginx/config/..2026_06_17_07_20_00.999999999/
├── nginx.conf (new data)
└── app-settings.yaml
Step 2: The kubelet atomically re-points the "..data" symlink to the new directory
..data ──> ..2026_06_17_07_20_00.999999999/
Step 3: The old version directory (version 1) is removed from the container filesystem
Because the application reads files through the nginx.conf -> ..data/nginx.conf symlink chain, the application immediately sees the new content once step 2 completes. However, if our application uses a library that caches the configuration file into memory at startup, the application still won’t notice this change until it receives a reload signal (like SIGHUP) or is triggered by an external watcher.
Immutable ConfigMaps for Large-Scale Optimization #
By default, Kubernetes watches every ConfigMap to detect data updates so files in volume mounts can be synchronized. However, for large-scale applications with thousands of Pods, this periodic watching process produces significant API query traffic overhead (API overhead) on the control plane.
If we have configuration that’s static and never changes during the deployment’s lifetime (e.g. built-in system configuration files), we can set the immutable: true property.
apiVersion: v1
kind: ConfigMap
metadata:
name: static-system-settings
namespace: production
immutable: true # Once created, the data below can no longer be changed
data:
SYSTEM_ARCH: "x86_64"
KERNEL_VERSION: "5.15"
Technical Advantages of Immutable ConfigMaps: #
- API Server Load Reduction: The kubelet on worker nodes no longer queries the API Server via watch to monitor this ConfigMap. This drastically reduces internal cluster network latency on large-scale clusters.
- Configuration Protection: Avoids human error from accidentally changing critical configuration parameters that can crash applications. If we need to change the configuration, we’re forced to create a new ConfigMap with a new name (versioning pattern).
ConfigMap Size and Scope Limitations #
Although flexible, ConfigMaps have several critical limitations we must understand before designing cluster configuration storage architecture:
- 1 Megabyte (MB) Size Limit: ConfigMaps are stored directly in the Kubernetes etcd database. To keep etcd read-write performance optimal, Kubernetes caps the maximum data size of one ConfigMap object at 1 MB. If we need to load very large configuration files (like a 50MB GeoIP database), don’t use a ConfigMap. Use external persistent volumes or download the file at startup using an init container.
- Not for Sensitive Data: Data inside ConfigMaps is stored as plain text without any encryption in etcd. Anyone with RBAC access to view ConfigMaps can read their contents. Never store passwords, tokens, or private keys in ConfigMaps. Use the Secret object for this purpose.
- Namespace-Scoped: ConfigMap objects only exist in the namespace where they’re created. Pods in the
productionnamespace can’t reference or mount ConfigMaps in thedevelopmentnamespace. To share global configuration, we must duplicate it in every namespace or leverage an external operator.
Pattern: Safe Updates Through ConfigMap Versioning #
One of the biggest dangers of updating a ConfigMap directly (in-place update) is losing control over when the configuration takes effect. Because the kubelet synchronizes volume changes randomly (depending on each node’s sync cycle), some of our Pods may run the new configuration while others still use the old one. If the new configuration contains typos, applications crash randomly and are hard to roll back instantly.
The best pattern for solving this problem is applying ConfigMap Versioning.
CONFIGMAP VERSIONING PATTERN (Safe & Controlled):
Step 1: Apply a new ConfigMap with a version suffix: "app-config-v2"
Step 2: Update the ConfigMap reference in the Deployment manifest to "app-config-v2"
Step 3: Kubernetes triggers an official rolling update:
- New Pods are created with config-v2 (verified by readiness probes)
- Old Pods with config-v1 keep running until new Pods are healthy
- Instant rollback is just re-pointing the Deployment back to config-v1
Let’s compare the update manifest between the dangerous in-place update approach (anti-pattern) and the versioning pattern (solution):
# ANTI-PATTERN: Directly updating the contents of the same ConfigMap on the cluster
# ✗ Triggers unsynchronized configuration transitions and breaks the automatic rollback mechanism
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-shared # Same name, only values changed manually
data:
APP_THEME: "dark-blue-broken-typo" # Parameter input error
---
# CORRECT: Create a new ConfigMap object and update the Deployment reference
# ✓ Enables the standard rolling update cycle and guarantees rollback safety
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v2 # Adds a new version to the object name
namespace: production
data:
APP_THEME: "dark-blue"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: billing-app
namespace: production
spec:
replicas: 3
template:
spec:
containers:
- name: billing
image: billing:v1.2.0
envFrom:
- configMapRef:
name: app-config-v2 # References the safe new version
By adopting the versioning pattern, we can ensure every configuration change goes through a rolling update testing cycle as rigorous as application code changes, minimizing downtime risk in production environments.
Summary #
- Configuration separation is key: Use ConfigMaps to implement the 12-Factor App principle by separating non-sensitive configuration files from container images.
- Understand the environment variable vs volume trade-off: Environment variables are static and need a Pod restart to update, while volume mounts update files dynamically through kubelet symlink swaps but require application hot-reload readiness.
- Don’t use subPath if you want auto-updates: Using the
subPathproperty on volume mounts permanently locks the file inode, so ConfigMap data updates won’t sync into the container.- Use the immutable property for efficiency: Set
immutable: trueon static ConfigMaps to reduce kubelet watch queries to the API Server, optimizing cluster scalability.- Respect the 1 MB size limit: Don’t store large files in ConfigMaps because they burden the etcd consensus database.
- Apply the versioning pattern: Always create new ConfigMap objects with different version names (e.g.
config-v2) to trigger safe, controlled rolling updates on Deployments.