Skip to main content
< All Topics

Kubernetes ConfigMaps for Decoupling Configuration

In the world of containerization, one golden rule we always follow is: “Build once, deploy everywhere.”

Imagine you have a Python application. You shouldn’t have to rebuild the entire Docker image just because you moved from a Development environment (where the database password might be admin123) to a Production environment (where the password is a complex, secure string).

Decoupling Configuration in Kubernetes is the art of keeping your application code separate from the settings it needs to run. We use two main objects for this:

  1. ConfigMaps: For plain text configuration (like URLs, UI colors, or properties files).
  2. Secrets: For sensitive data (like passwords, API keys, and certificates).

Basically, the container image is your logic, and Kubernetes injects the configuration into it at runtime.


ConfigMaps: Non-Sensitive Data

Imagine you have built a robot (your Application Container). Now, you want to send this robot to three different environments: a desert (Development), a rainforest (Testing), and the moon (Production).

You do not want to rebuild the robot every time just to change its settings. Instead, you handover the “instruction sheet” to robot when it lands.

  • In the desert, the instruction sheet says: “Drink water every 10 mins.”
  • On the moon, the instruction sheet says: “Turn on oxygen tank.”

In Kubernetes, that “Instruction Sheet” is called a ConfigMap. It decouples (separates) your application code from the specific configuration data it needs to run, like database hostnames, UI colors, or log levels.

Key Characteristics to Remember
  • ConfigMaps are strictly for non-confidential data (no passwords!).
  • They act as a dictionary (Key-Value pairs) injected into Pods.
  • You can inject them as Environment Variables (simple strings) or Mounted Files (complex configs).
  • Updating a ConfigMap does not automatically restart the Pod; the application must handle the change (or be restarted).
Injection Methods
FeatureMethod A: Environment VariablesMethod B: Volume Mount (File)
Best Use CaseSimple settings (DB_URL, COLOR, DEBUG_MODE)Complete config files (nginx.conf, settings.json)
ComplexityVery Simple (Beginner Friendly)Moderate (Advanced)
UpdatesRequires Pod restart to reflect changes.Updates automatically in container (delayed), but app must reload it.
12-Factor AppFully Compliant.Partially Compliant (files are “state”, but useful).

In the modern DevSecOps world, we strictly follow the 12-Factor App methodology, which states that “Config” should be stored in the environment, not the code.

Why use ConfigMaps?

  1. Portability: You build your Docker image once. You can then deploy that exact same image to Dev, QA, and Prod just by attaching a different ConfigMap.
  2. Organization: It keeps your massive configuration files (like redis.conf or prometheus.yml) out of your application code, keeping your git repositories clean.

How it works: Kubernetes stores these ConfigMaps in its internal database (etcd). When a Pod starts, the Kubelet (the node agent) fetches this data and presents it to the container as either environment variables (e.g., echo $DB_HOST) or as a physical file inside the container’s file system (e.g., /etc/config/app.properties).

  • Method A: Environment Variables (The Easy Way)
    • This is the most common method. You define keys like DB_HOST in the ConfigMap.
    • Inside the container, your code reads them just like any system variable.
    • Tip: Use envFrom to load all keys at once. It saves typing! But be careful if you change a key name in the ConfigMap, your code might crash because it can’t find the old variable name.
  • Method B: Volume Mounts (The File Way)
    • This is “mounting” a virtual USB drive containing your config files.
    • If you have a legacy application that requires a file at /etc/app/config.ini, this is the method you use.
    • Gotcha: If you mount a ConfigMap to a specific folder (like /etc/app), it might overwrite everything else in that folder. (See “SubPath” in advanced notes to fix this).
DevSecOps Architect Level

As an architect, you need to worry about Lifecycle Management and Scale.

  1. Immutability (immutable: true):
    • Concept: In Kubernetes v1.21+, you can mark a ConfigMap as immutable.
    • Benefit: This stops accidental updates that could break production. It also significantly boosts performance because the Kubelet stops “watching” the API server for changes to that ConfigMap.
  2. The “Update Delay” (Cache Propagation):
    • Concept: When you update a ConfigMap mounted as a volume, the file inside the container will update, but it takes time. The Kubelet sync period (default 1 minute) + API server cache means it’s not instant.
    • Architect Advice: Do not rely on instant updates. Design your app to handle a ~60-second delay.
  3. Handling “SubPath” mounts:
    • Problem: Mounting a volume to /etc/nginx/ hides all existing files in that directory.
    • Solution: Use subPath in your Pod spec to mount only the specific file (e.g., nginx.conf) without hiding the rest of the folder.
    • Warning: Files mounted with subPath do not auto-update when the ConfigMap changes! You must restart the Pod.

Essential Tools for Architects:

  1. Reloader (Stakater):
    • Function: Watches for ConfigMap changes and performs a rolling restart on Deployments. This solves the “app doesn’t see the update” problem.
    • Website: https://github.com/stakater/Reloader
  2. Kustomize (ConfigMapGenerator):
    • Function: It generates ConfigMaps with a unique hash suffix (e.g., config-h92a4). When config changes, the hash changes, forcing a new Deployment rollout. This is the GitOps standard way to handle configs.
    • Website: https://kustomize.io/
  • Size Limit: A ConfigMap cannot exceed 1 MiB. If you have a massive configuration file (like a huge XML or JSON dataset), ConfigMap is not the right tool. You should use a distinct Persistent Volume or download the file from an external blob storage (like S3) at startup.
  • Binary Data: While mostly for text, ConfigMaps can store binary data (base64 encoded), but it is rare.
  • Scope: ConfigMaps are Namespaced. A Pod in “Namespace A” generally cannot read a ConfigMap in “Namespace B” directly.
Key Components
  1. Data Section: The actual key-value pairs (server.port: "8080").
  2. Metadata: Name, Namespace, Labels.
  3. Pod Reference: The envFrom or volumeMounts section in the Deployment YAML.
Use Cases
  • Database Connection Strings: (Host/Port only—passwords go in Secrets!).
  • UI Customization: Themes, Welcome Messages.
  • Feature Toggles: ENABLE_NEW_UI: "false".
  • Operator Configs: Settings for Prometheus, Grafana, Nginx.
Benefits
  • Decoupling: Change config without rebuilding the container image.
  • Consistency: Use the same image across all environments.
  • Version Control: You can keep your ConfigMap YAMLs in Git to track changes over time.
Best Practices
  • Naming Convention: Use clear names like app-name-dev-config or app-name-prod-config.
  • Don’t put Secrets here: Never put API keys or passwords in ConfigMaps. They are plain text!
  • Use Generators: In production pipelines, use Kustomize or Helm to generate ConfigMaps rather than writing them manually.
Technical Challenges, Limitations, & Common Issues
ProblemRoot CauseSolution
“CreateContainerConfigError”The Pod cannot start because the referenced ConfigMap is missing.Check spelling! Ensure the ConfigMap exists in the same namespace as the Pod.
App not updatingConfigMap changed, but app didn’t pick it up.Apps usually load config once at startup. Use Reloader or manually restart the Pod (kubectl rollout restart deployment ...).
SubPath UpdatesFiles mounted with subPath never update.This is by design in K8s. Avoid subPath if you need hot-reloading, or accept that you must restart the Pod.
Space/FormattingYAML indentation errors in the data block.Use the pipe symbol `

https://kubernetes.io/docs/concepts/configuration/configmap

https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap


Configure a Pod to Use a ConfigMap

So, you have created a ConfigMap (a box of settings). Now, the big question is: “How do I actually connect this box to my application?”

Configuring a Pod to use a ConfigMap is like plugging a USB drive into a laptop or setting environment variables on your PC. You are telling Kubernetes: “Hey, when you start this container, please take these settings from the ConfigMap and put them inside the container so my app can read them.”

Key Characteristics to Remember
  • Use envFrom to inject all key-values from a ConfigMap as environment variables at once.
  • Use valueFrom to inject a single specific key as an environment variable.
  • Use volumes and volumeMounts to mount the ConfigMap as a directory of files.
  • Crucial: Environment variables are set only at startup. If you change the ConfigMap, you must restart the Pod to see the changes.
MethodSyntax KeywordBest ForUpdates?
All Env VarsenvFrom12-Factor Apps, simple key-value pairs.No (Requires Restart)
Single Env VarvalueFromRemapping names (e.g., Map key db_host to Env Var HOST).No (Requires Restart)
Volume MountvolumeMountsApps requiring config files (Nginx, Prometheus).Yes (Eventually, ~60s delay)
SubPath MountsubPathInjecting one file into an existing folder.No (Requires Restart)

Method A: The “Environment Variable” Style (Most Common)

This is the cleanest way for modern applications.

1. Injecting ALL keys (envFrom) This takes every key in the ConfigMap and makes it an environment variable.

Copy the code below into a file named demo-envfrom.yaml.

YAML
# 1. First, we create the ConfigMap with multiple keys
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
data:
  # These are the keys that will become Environment Variables
  UI_COLOR: "cyan"
  DB_HOST: "postgres.production.svc"
  LOG_LEVEL: "debug"
  MAX_RETRIES: "5"

---
# 2. Next, we create the Pod that consumes ALL keys at once
apiVersion: v1
kind: Pod
metadata:
  name: env-all-demo
spec:
  containers:
    - name: my-app
      image: busybox
      # We command it to sleep so the Pod stays 'Running' for us to check
      command: [ "sleep", "3600" ]
      envFrom:              # <--- Magic Keyword
        - configMapRef:
            name: my-config # Must match the ConfigMap name above

How to Run and Verify

1. Apply the configuration:

Bash
kubectl apply -f demo-envfrom.yaml

2. Verify the Environment Variables: Once the Pod is running (it takes a few seconds), run this command to print all environment variables inside the container. You will see your ConfigMap values in the list.

Bash
kubectl exec env-all-demo -- env

Expected Output: You should see standard system variables mixed with your custom ones:

Bash
...
HOSTNAME=env-all-demo
UI_COLOR=cyan               <--- Injected!
DB_HOST=postgres.production.svc <--- Injected!
LOG_LEVEL=debug             <--- Injected!
MAX_RETRIES=5               <--- Injected!
HOME=/root
...

3. Cleanup (Optional): To delete the resources when you are done:

Bash
kubectl delete -f demo-envfrom.yaml

2. Injecting a SPECIFIC key (valueFrom) Use this if you want to rename the variable or only need one specific value.

This approach is best when your application expects an environment variable named DATABASE_HOST, but your ConfigMap stores it as db_host. You can “map” or “rename” it on the fly.

Copy the code below into a file named demo-single-env.yaml.

YAML
# 1. Create the ConfigMap with the source data
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
data:
  # The source key is lowercase 'db_host'
  db_host: "postgres-prod.svc.cluster.local"
  other_key: "ignore-me"

---
# 2. Create the Pod that picks ONLY the 'db_host' key
apiVersion: v1
kind: Pod
metadata:
  name: env-single-demo
spec:
  containers:
    - name: my-app
      image: busybox
      # Keep the container running so we can inspect it
      command: [ "sleep", "3600" ]
      env:
        - name: DATABASE_HOST  # <--- The name your APP looks for
          valueFrom:
            configMapKeyRef:
              name: my-config  # The ConfigMap source
              key: db_host     # The specific key to grab

How to Run and Verify

1. Apply the configuration:

Bash
kubectl apply -f demo-single-env.yaml

2. Verify the Environment Variable: Once the Pod is running, check the environment variables inside. You will see DATABASE_HOST but not other_key.

Bash
kubectl exec env-single-demo -- env | grep DATABASE_HOST

Expected Output:

Bash
DATABASE_HOST=postgres-prod.svc.cluster.local

3. Cleanup:

Bash
kubectl delete -f demo-single-env.yaml

💡 Why do this?

This “mapping” gives you freedom. You can have a shared ConfigMap called global-settings with 50 keys, but a specific Pod might only need one of them, and it might need it under a completely different name to satisfy legacy code requirements.


Method B: The “Volume Mount” Style (File Based)

Use this for legacy apps or complex configurations (like nginx.conf).

This method is powerful because it allows you to inject entire configuration files (like nginx.conf, redis.conf, or JSON files) without rebuilding the Docker image.

Copy the code below into a file named demo-volume.yaml.

YAML
# 1. Create the ConfigMap acting as our "File Store"
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
data:
  # In Volume mounts, the KEY becomes the FILENAME
  # The VALUE becomes the FILE CONTENT
  app.properties: |
    color=blue
    feature_toggle=true
    max_items=50
  
  extra-settings.json: |
    { "logging": "verbose", "mode": "production" }

---
# 2. Create the Pod that mounts this ConfigMap as a folder
apiVersion: v1
kind: Pod
metadata:
  name: volume-demo
spec:
  containers:
    - name: my-app
      image: nginx:alpine  # Using lightweight Nginx
      volumeMounts:
        - name: config-vol      # 1. Matches the volume name below
          mountPath: /etc/config # 2. The folder where files will appear
          readOnly: true        # Good practice: prevent app from changing config
  volumes:
    - name: config-vol          # 3. Define the volume source
      configMap:
        name: my-config         # 4. Point to the ConfigMap above

How to Run and Verify

1. Apply the configuration:

Bash
kubectl apply -f demo-volume.yaml

2. Verify the Files Exist: Once the Pod is running, list the contents of the directory we mounted (/etc/config). You will see that Kubernetes has converted your ConfigMap keys into physical files.

Bash
kubectl exec volume-demo -- ls -l /etc/config

Expected Output:

Bash
lrwxrwxrwx 1 root root ... app.properties -> ...
lrwxrwxrwx 1 root root ... extra-settings.json -> ...

(Note: You might see them as symlinks (arrows ->); this is how Kubernetes manages live updates for config files.)

3. Read the File Content: Check the content of one of the files to confirm the data is there.

Bash
kubectl exec volume-demo -- cat /etc/config/app.properties

Output:

Bash
color=blue
feature_toggle=true
max_items=50

4. Cleanup:

Bash
kubectl delete -f demo-volume.yaml

💡 Key Takeaway

If you update the ConfigMap while the Pod is running, the files inside /etc/config will eventually update (usually within 60 seconds) without restarting the Pod. This is a huge advantage for “hot-reloading”.


SubPath Mount subPath

The Problem with Normal Mounts

If you mount a Volume to a directory (e.g., /etc/nginx/), it hides everything else that was originally in that directory. The directory becomes “empty” except for your mounted files.

The Solution: subPath

Using subPath allows you to inject a single file into an existing directory without hiding the other files that are already there.

In this example, we will replace the default index.html page of Nginx, but we will keep the other files (like 50x.html) that exist in the same folder.

Copy the code below into a file named demo-subpath.yaml

YAML
# 1. Create a ConfigMap with our custom HTML file
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-html-config
data:
  # The key name matches the subPath below
  custom-index.html: |
    <html>
      <body>
        <h1>🚀 Hello from SubPath!</h1>
        <p>I replaced index.html, but 50x.html is still here!</p>
      </body>
    </html>

---
# 2. The Pod mounting ONLY that single file
apiVersion: v1
kind: Pod
metadata:
  name: subpath-demo
spec:
  containers:
    - name: my-app
      image: nginx:alpine
      volumeMounts:
        - name: html-vol
          # 1. TARGET: Full path including the filename
          mountPath: /usr/share/nginx/html/index.html
          # 2. SOURCE: The specific key from the ConfigMap
          subPath: custom-index.html
  volumes:
    - name: html-vol
      configMap:
        name: my-html-config
How to Run and Verify

1. Apply the configuration:

Bash
kubectl apply -f demo-subpath.yaml

2. Verify the Replacement: Check that our custom index.html is serving the new content.

Bash
kubectl exec subpath-demo -- cat /usr/share/nginx/html/index.html

Output:

Bash
<h1>🚀 Hello from SubPath!</h1>
...

3. Verify Other Files Still Exist (Crucial Step): This is the magic of subPath. If we had done a normal mount, this command would fail because 50x.html (which comes with the Nginx image) would be gone.

Bash
kubectl exec subpath-demo -- ls -l /usr/share/nginx/html/

Expected Output:

Bash
-rw-r--r-- ... 50x.html      <-- STILL EXISTS!
-rw-r--r-- ... index.html    <-- Our custom file

4. Cleanup:

Bash
kubectl delete -f demo-subpath.yaml

🚨 Important Limitation

Files mounted using subPath do NOT auto-update. If you edit the ConfigMap, a normal volume mount will update the file inside the container after ~60 seconds. A subPath mount will never update until the Pod is restarted. This is a known Kubernetes trade-off.


  • Restarting is Key: Beginners often change a ConfigMap and wonder why the app didn’t change. For Environment Variables, the container assumes the values are constant from the moment it starts. You must delete the Pod (kubectl delete pod ...) to let the Deployment recreate it with new values.
  • Overwriting Directories: If you use a Volume Mount at /etc/app, typically everything else in that folder is hidden, and only your ConfigMap files are visible. Be careful not to mount over essential system folders!

Immutable ConfigMaps (immutable: true) For high-scale production, mark your ConfigMaps as immutable.

YAML
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
immutable: true  # <--- Architect's Choice
data:
  ...
  • Why? It protects you from “Configuration Drift” (accidental changes breaking live apps) and reduces load on the Kubernetes API server because Kubelets stop polling for updates.

Hot Reloading Sidecars For 24/7 systems where you cannot restart Pods, use a Sidecar Container (like a config-watcher) that shares the volume. When the file updates, the sidecar signals the main app (e.g., kill -HUP 1) to reload the configuration in memory.

Key Components
  1. envFrom: Bulk injection of variables.
  2. valueFrom: Precise injection of specific variables.
  3. volumeMounts: The path inside the container where files land.
  4. volumes: The definition linking the abstract volume to the specific ConfigMap resource.
Use Cases
  • Nginx/Apache: Injecting httpd.conf or nginx.conf.
  • Spring Boot: Injecting application.properties.
  • Node.js: Injecting process.env variables for backend API URLs.
Benefits
  • Separation of Concerns: Developers write code; DevOps manage config.
  • Security: You can restrict who can edit ConfigMaps using RBAC, separate from who can deploy Pods.
Technical Challenges, Limitations, & Common Issues
ProblemRoot CauseSolution
Pod Status: CreateContainerConfigErrorThe ConfigMap name in the Pod Spec does not match the actual ConfigMap name.Double-check spelling. Ensure both are in the same Namespace.
Old Config PersistingUsing Env Vars and expecting auto-updates.Restart the Pod (kubectl rollout restart deployment <name>).
Files HiddenMounting a volume to a non-empty directory.Use subPath to mount single files, or mount to a new, empty directory.
Invalid Variable NamesKeys in ConfigMap have dashes (e.g., db-host).Environment variables cannot have dashes in many shells. Use underscores (db_host) or valueFrom to rename them.

Contents
Scroll to Top