Definition
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.
# 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
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.
# 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"
# 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 |
# 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.
# 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
# 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.
# 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.
# 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
# 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
# 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.
# 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.
# 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.
# 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: {}
# 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
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.
---