3.4 ConfigMaps, Secrets & Application Configuration
Key Takeaways
- ConfigMaps hold non-confidential key-value configuration data so that the same immutable container image can run unchanged across development, staging, and production.
- Secrets hold small amounts of sensitive data and are base64-encoded, not encrypted — encryption at rest in etcd must be enabled separately by the cluster administrator.
- ConfigMaps and Secrets can be consumed as environment variables or mounted as volumes; only volume mounts pick up updates without a Pod restart.
- Both objects are namespaced and capped at roughly 1 MiB, which is why large payloads belong in a PersistentVolume or an external store rather than a ConfigMap.
- Common Secret types include Opaque (the default), kubernetes.io/dockerconfigjson for registry credentials, kubernetes.io/tls for certificate pairs, and kubernetes.io/service-account-token.
3.4 ConfigMaps, Secrets & Application Configuration
Quick Answer: A ConfigMap stores non-confidential configuration as key-value pairs; a Secret stores small amounts of sensitive data such as passwords, tokens, and TLS keys. Both are namespaced objects capped at about 1 MiB, and both can be injected into a Pod either as environment variables (read once at container start) or as a mounted volume (updated in place, without a restart). Secrets are base64-encoded, not encrypted — encryption at rest is a separate cluster-level setting.
The Twelve-Factor App rule "store config in the environment" is not an abstract principle in Kubernetes: it is an API. Building one immutable image and injecting environment-specific values at runtime is the entire reason ConfigMaps and Secrets exist, and KCNA tests both the mechanism and its security limits.
1. Why Configuration Must Leave the Image
If a database URL is baked into a container image, you need a different image per environment. That breaks immutability, breaks promotion ("the exact artifact I tested is the artifact I ship"), and breaks rollback. Kubernetes solves it by keeping configuration in cluster objects and binding it to the Pod at scheduling time.
ONE IMAGE THREE ENVIRONMENTS
myapp:v2.1.0 ──┬──► dev + ConfigMap/dev-config + Secret/dev-db
├──► staging + ConfigMap/staging-config + Secret/staging-db
└──► prod + ConfigMap/prod-config + Secret/prod-db
2. ConfigMaps
A ConfigMap is a namespaced API object holding key-value pairs. Values may be short strings or entire file contents (an nginx.conf, a application.properties, a JSON policy document).
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
namespace: production
data:
LOG_LEVEL: "info"
FEATURE_CHECKOUT_V2: "true"
nginx.conf: |
server {
listen 8080;
location /healthz { return 200; }
}
Consuming a ConfigMap
There are three consumption patterns, and the difference between them is a favourite exam distinction:
- Single value as an environment variable —
valueFrom.configMapKeyRefpulls one named key. - All keys as environment variables —
envFrom.configMapRefimports every key at once. - Mounted as a volume — each key becomes a file whose name is the key and whose contents are the value.
spec:
containers:
- name: web
image: myapp:v2.1.0
env:
- name: LOG_LEVEL # pattern 1: one key
valueFrom:
configMapKeyRef:
name: web-config
key: LOG_LEVEL
envFrom:
- configMapRef: # pattern 2: every key
name: web-config
volumeMounts:
- name: nginx-conf # pattern 3: keys become files
mountPath: /etc/nginx/conf.d
volumes:
- name: nginx-conf
configMap:
name: web-config
items:
- key: nginx.conf
path: default.conf
The update rule (memorise this). Environment variables are resolved once, when the container starts. Changing the ConfigMap afterwards has no effect until the Pod is recreated. Mounted volumes are different: the kubelet refreshes the projected files periodically, so a config file mounted from a ConfigMap can change under a running process — provided the application actually re-reads it.
Immutable ConfigMaps
Setting immutable: true on a ConfigMap or Secret prevents any further edits. This is not just a safety measure: it lets the kubelet stop watching the object, which measurably reduces API server load in clusters with thousands of Pods. To change an immutable object you create a new one and update the Pod template — which is exactly the immutable-infrastructure pattern.
3. Secrets
A Secret looks almost identical to a ConfigMap but is intended for sensitive values. The differences that matter:
| Aspect | ConfigMap | Secret |
|---|---|---|
| Intended data | Non-confidential settings | Passwords, tokens, keys, certificates |
| Wire/stored encoding | Plain text in data | base64 in data, or plain text in stringData on write |
| Encryption at rest | No | Not by default — requires an EncryptionConfiguration on the API server |
| kubelet handling | Written to node disk | Held in tmpfs (memory) when mounted, never written to node disk |
| Size limit | ~1 MiB | ~1 MiB |
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData: # stringData accepts plain text; the API server base64-encodes it
username: appuser
password: "S3cur3-P@ss"
Common Secret Types
type | Purpose |
|---|---|
Opaque | The default: arbitrary user-defined key-value data |
kubernetes.io/dockerconfigjson | Private registry credentials, referenced by imagePullSecrets |
kubernetes.io/tls | A TLS certificate/key pair, consumed by Ingress spec.tls.secretName |
kubernetes.io/service-account-token | A ServiceAccount API token (largely superseded by projected tokens) |
kubernetes.io/basic-auth, kubernetes.io/ssh-auth | Structured credential formats |
base64 Is Not Encryption
This is the single most-tested security point in the topic. echo cGFzc3dvcmQ= | base64 -d reveals the value instantly — base64 is a transport encoding, nothing more. To actually protect Secrets you need a combination of:
- Encryption at rest: an
EncryptionConfigurationonkube-apiserver, ideally backed by an external KMS provider so the key never lives on the control plane disk. - RBAC: restrict
get,list, andwatchonsecretsto the workloads and humans that genuinely need them. - External secret stores: HashiCorp Vault, cloud secret managers, or the External Secrets Operator / Secrets Store CSI Driver, which sync material in at runtime instead of storing it in etcd at all.
automountServiceAccountToken: falsefor Pods that never call the API server.
4. Choosing an Injection Method
| Requirement | Use |
|---|---|
| A handful of scalar settings the app reads at boot | Environment variables |
| A whole config file, certificate, or keystore | Volume mount |
| Values that must change without a redeploy | Volume mount (plus an app that re-reads) |
| Registry credentials | imagePullSecrets referencing a dockerconfigjson Secret |
| Anything larger than about 1 MiB | Neither — use a PersistentVolume or an object store |
Design caution: injecting a Secret as an environment variable exposes it to anything that can read
/proc/<pid>/environ, and it commonly leaks into crash dumps and log lines. Volume mounts are the safer default for genuinely sensitive material.
An operator edits a ConfigMap that a running Pod consumes as environment variables. What happens to the running container?
What level of protection does a standard Kubernetes Secret provide for its stored values?
Which Secret type is referenced by a Pod's imagePullSecrets field to authenticate against a private container registry?