Back to Kubernetes: Core Concepts
Q10Question 10 of 16

ConfigMap vs Secret, and Why Pods Do Not Auto-Reload Config Changes

Key Concepts
  • A ConfigMap stores non-sensitive configuration as key-value pairs such as feature flags, log levels, and timeouts.
  • A Secret stores sensitive data like database passwords and API keys, base64-encoded and ideally encrypted at rest.
  • Environment variable injection is read once at container start, so changes never take effect without a Pod restart.
  • Volume-mounted ConfigMaps are refreshed on disk by the kubelet, but subPath mounts never update.
  • Most applications cache config at startup, so even refreshed files on disk do not change behaviour without a reload.
  • The safest pattern is immutable, versioned ConfigMaps referenced from the Pod template so rollback reverts config too.

Definition

ConfigMap holds open settings, Secret holds sensitive values, and neither reliably takes effect without a Pod restart or a reloader because env vars are read once and file caches are stale.

A ConfigMap and a Secret are Kubernetes objects for injecting configuration into Pods.

  • ConfigMap – stores non-sensitive data as key-value pairs: feature flags, log levels, timeouts, upstream URLs.
  • Secret – stores sensitive data such as passwords, tokens, and TLS keys; values are base64-encoded and should be encrypted at rest with RBAC restrictions.
  • Consumption – both can be consumed as environment variables (read once at start) or as mounted files (refreshed by kubelet after a delay).
  • Reload limitation – env vars never change without a restart; mounted files are updated but most apps cache the file at startup, so a restart or reloader is still needed.

At the National Bank of India, a ConfigMap is the branch's public notice board showing the daily interest rate, office hours, and API timeout. A Secret is the vault combination and the signing stamp, kept locked and shown only to authorized staff. The reason a teller does not notice when you change the notice board is that the teller memorized the rate when they clocked in. To use the new rate they must clock out and clock back in. That clock-out-clock-in is the Pod restart.

Understanding the Concepts

What is a ConfigMap?

A ConfigMap stores non-sensitive configuration data as key-value pairs. It is used for feature flags, log levels, timeouts, upstream URLs, and application configuration files.

YAML
# configmap.yaml - configuration for the transfer service
apiVersion: v1
kind: ConfigMap
metadata:
  name: transfer-config
  namespace: prod
data:
  LOG_LEVEL: "INFO"
  TRANSFER_TIMEOUT_MS: "5000"
  MAX_TRANSFER_AMOUNT: "1000000"
  application.yml: |
    spring:
      datasource:
        url: jdbc:postgresql://ledger-db:5432/banking
        hikari:
          maximum-pool-size: 20
BASH
kubectl get configmap transfer-config -n prod -o yaml
kubectl describe configmap transfer-config -n prod

What is a Secret?

A Secret stores sensitive data such as database passwords, API keys, TLS private keys, and JWT signing keys. The values are base64-encoded, which is encoding and not encryption.

YAML
# secret.yaml - database credentials
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: prod
type: Opaque
data:
  DB_USERNAME: cmFqZXNo         # base64 of "rajesh"
  DB_PASSWORD: c2VjdXJlUGFzcw== # base64 of "securePass"
BASH
# Create a Secret from literal values
kubectl create secret generic db-credentials \
  --from-literal=DB_USERNAME=rajesh \
  --from-literal=DB_PASSWORD=securePass \
  -n prod

# Base64 encoding is NOT encryption
echo "c2VjdXJlUGFzcw==" | base64 -d
# securePass  <- anyone with access can decode it

How do ConfigMap and Secret differ?

| Aspect | ConfigMap | Secret |
|---|---|---|
| Data type | Non-sensitive | Sensitive (passwords, tokens, keys) |
| Encoding | Plain text | Base64-encoded |
| Encryption at rest | Not by default | Should enable etcd encryption |
| RBAC | Standard access | Tightly restricted to owning service account |
| Size limit | 1 MiB | 1 MiB |
| Use case | Feature flags, log levels | DB passwords, API keys, TLS certs |

YAML
# WRONG - database password in a ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: transfer-config
data:
  DB_PASSWORD: "securePass"   # visible to anyone who can read ConfigMaps

# CORRECT - database password in a Secret with restricted RBAC
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  DB_PASSWORD: c2VjdXJlUGFzcw==

How do you consume ConfigMaps and Secrets in a Pod?

There are two ways: as environment variables or as mounted files. The consumption mode determines reload behaviour.

YAML
# Method 1: Environment variables (read once at startup)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: transfer-service
spec:
  template:
    spec:
      containers:
        - name: transfer
          image: nbi-registry.example.com/transfer-service:2.0.0
          envFrom:
            - configMapRef:
                name: transfer-config
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: DB_PASSWORD
YAML
# Method 2: Volume mount (files refreshed by kubelet)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: transfer-service
spec:
  template:
    spec:
      containers:
        - name: transfer
          volumeMounts:
            - name: config-volume
              mountPath: /etc/config
              readOnly: true
      volumes:
        - name: config-volume
          configMap:
            name: transfer-config

Why do Pods not pick up ConfigMap changes automatically?

Environment variables are a point-in-time snapshot. The container's environment is fixed when the process starts. If you edit the ConfigMap afterward, the running container keeps the old value forever.

Volume-mounted values are different but still tricky. The kubelet periodically syncs the files, so after a delay (up to about a minute) the file on disk reflects the new ConfigMap. However, a subPath mount never updates at all. And most applications read a config file once at startup into memory and never re-read it, so even though the file changed, the process still uses the cached value.

BASH
# Scenario: operator raises the transfer fee in the ConfigMap
kubectl edit configmap transfer-config -n prod
# Changed fee.percentage from 0.25 to 0.50

# If consumed as an environment variable:
# The running Pod still uses 0.25 forever
# Only a restart picks up 0.50

# If consumed as a volume mount:
# The file on disk updates after ~60 seconds
# But the Java app read it into memory at startup and never re-reads
# The running process still uses 0.25

How do you apply a ConfigMap or Secret change?

There are several approaches, from explicit to automatic.

BASH
# Approach 1: Trigger a rollout restart
kubectl rollout restart deployment/transfer-service -n prod
# Recreates all Pods so they read the new config at startup

# Approach 2: Immutable, versioned ConfigMap per release
# This is the cleanest pattern for banking
YAML
# Create a new ConfigMap name per release
apiVersion: v1
kind: ConfigMap
metadata:
  name: transfer-config-v8   # new name for this release
data:
  fee.percentage: "0.50"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: transfer-service
spec:
  template:
    spec:
      containers:
        - name: transfer
          envFrom:
            - configMapRef:
                name: transfer-config-v8   # part of Pod template
# Rolling update applies the new config
# Rollback reverts to transfer-config-v7 automatically
BASH
# Approach 3: Reloader (Stakater) watches and triggers rollout automatically
helm install reloader stakater/reloader -n kube-system

# Annotate the Deployment to enable auto-reload
kubectl annotate deployment transfer-service \
  reloader.stakater.com/auto="true" -n prod

What about subPath mounts?

A subPath mount never updates. This is a known limitation that catches many teams.

YAML
# WRONG - subPath mount never picks up ConfigMap changes
volumeMounts:
  - name: config-volume
    mountPath: /etc/config/application.yml
    subPath: application.yml   # this file will NEVER update

# CORRECT - mount the whole directory, then reference the file inside
volumeMounts:
  - name: config-volume
    mountPath: /etc/config     # the whole directory updates

What are common mistakes?

The most common mistake is assuming a ConfigMap or Secret change is live immediately. In a bank, an operator changed the transfer fee in a ConfigMap and was surprised that transfers kept charging the old fee because the service read it as an environment variable at startup.

BASH
# WRONG assumption: edit ConfigMap and expect immediate effect
kubectl edit configmap transfer-config -n prod
# Changed fee.percentage to 0.50
# But the transfer service still charges 0.25
# The operator thinks the change failed

# CORRECT: edit ConfigMap, then restart to apply
kubectl edit configmap transfer-config -n prod
kubectl rollout restart deployment/transfer-service -n prod
# Now all Pods pick up 0.50

What should a senior engineer know?

In a payment service, implicit auto-reload of a live Secret is usually undesirable. You want config changes to go through a controlled, logged rollout, not silently mutate a running process handling money.

Secrets should be encrypted at rest by enabling etcd encryption or using an external provider like HashiCorp Vault. RBAC should restrict Secret access to only the owning service account.

YAML
# Enable etcd encryption for Secrets
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-key>
      - identity: {}
BASH
# Restrict Secret access with RBAC
kubectl create role secret-reader \
  --verb=get --resource=secrets \
  --resource-name=db-credentials -n prod

kubectl create rolebinding transfer-service-secret-reader \
  --role=secret-reader \
  --serviceaccount=prod:transfer-service -n prod

Visual Overview

DIAGRAM
  ConfigMap / Secret Consumption and Reload Behaviour
  =====================================================

  Environment Variable Injection
  ───────────────────────────────
  Container starts
       │
       │  envFrom: configMapRef
       │  Copies values into process env
       ▼
  Process runs with env[FEE]=0.25
       │
       │  ConfigMap edited: FEE=0.50
       │  (has NO effect on running container)
       ▼
  Process still uses env[FEE]=0.25  ← stale forever
       │
       │  kubectl rollout restart
       ▼
  New Pod starts → env[FEE]=0.50  ← updated


  Volume Mount Injection
  ───────────────────────
  Container starts
       │
       │  volumeMount: /etc/config/
       │  App reads /etc/config/app.yml into memory
       ▼
  Process runs with cached fee=0.25
       │
       │  ConfigMap edited: fee=0.50
       │  kubelet syncs file after ~60s
       │  File on disk now says 0.50
       │  But app has cached 0.25 in memory
       ▼
  Process still uses fee=0.25  ← stale in memory
       │
       │  App must watch file + hot-reload, OR restart
       ▼
  Process uses fee=0.50

Key Takeaway

ConfigMaps hold non-sensitive settings and Secrets hold sensitive values like database passwords and API keys. The line between them must be strict: a credential never belongs in a ConfigMap. A Secret's base64 encoding is not encryption, so in a bank you also enable encryption at rest and restrict access with RBAC.

Both can be consumed as environment variables or as mounted files, and that choice determines reload behaviour. Environment variables are a startup snapshot and never change without a restart. Volume-mounted files are refreshed by the kubelet after a delay, but subPath mounts never update, and most applications cache the file at startup, so a restart or reloader is still needed.

The safest pattern for a banking service is to treat config changes as deployments. Make configuration immutable and versioned by creating a new ConfigMap name per release and referencing it from the Pod template. That way a rolling update applies the new config, and a rollback automatically reverts to the matching old config.

Remember one sentence: Pods do not pick up ConfigMap or Secret changes automatically because env vars are read once and files are cached, so treat every config change as a deployment.

---

Share
Use ← → arrow keys to navigate