Baking a database URL or an API key directly into a container image means rebuilding and redeploying the image just to change a config value — ConfigMaps and Secrets exist specifically to break that coupling, one for ordinary settings and one for anything sensitive.
3 min read
An image containing DATABASE_URL=postgres://prod-db... hardcoded into
its build has two real problems: the exact same image can't be reused
across staging and production (they need different values), and changing
that URL means rebuilding and redeploying the whole image just for a
config change that has nothing to do with the application's actual code.
Kubernetes' answer is to keep configuration as a separate object,
referenced by a Pod rather than baked into it.
A ConfigMap holds key-value configuration data as a standalone object:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
API_TIMEOUT_MS: "5000"A Pod can consume this either as environment variables or as mounted files, without the application code needing to know or care which:
containers:
- name: app
envFrom:
- configMapRef:
name: app-configChanging LOG_LEVEL now means editing the ConfigMap and redeploying the
Pods that reference it — the application image itself never needs to
change for a configuration-only update.
A Secret has the identical shape and API as a ConfigMap, but is meant for sensitive values — API keys, database passwords, TLS certificates:
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
DATABASE_PASSWORD: cGFzc3dvcmQxMjM= # base64-encoded, NOT encryptedA Secret's values are base64-encoded, not encrypted — base64 is a reversible encoding, not a security measure, and anyone with read access to the Secret object (or etcd itself, if it isn't separately encrypted at rest) can trivially decode it back to plaintext with a single command. Treating a Secret as "safe by default" because it looks different from a ConfigMap is a genuine, common misunderstanding. Real protection requires additional layers: encrypting etcd at rest (a cluster-level configuration, not a per-Secret setting), tightly scoping RBAC so only the Pods and people who genuinely need a Secret can read it (lesson 11), and often an external secrets manager (like the ones covered in the Cloud Computing domain) that Kubernetes Secrets can integrate with rather than replace entirely for the most sensitive values.
Updating a ConfigMap or Secret doesn't automatically restart Pods that already loaded its old values as environment variables — environment variables are set once, at container start. Pods mounting a ConfigMap or Secret as a file instead do eventually see updated content without a restart (Kubernetes updates the mounted file in place), but an application that only reads that file once at startup won't notice the change either way. This is a common source of "I updated the config but nothing changed" confusion — the fix is either restarting the affected Pods after a config change, or designing the application to periodically re-read its config file if live updates without a restart are genuinely needed.
A Secret manifest committed to a git repository defeats much of the point, since the sensitive value (even base64-encoded) becomes visible to anyone with repository access and stays in git history indefinitely even after being changed later. Real deployments generate Secrets at deploy time from a separate secrets store, or use tools specifically designed for encrypting secrets safely within version control (like Sealed Secrets), rather than committing plain Secret manifests directly.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why is baking configuration directly into a container image a problem?
2. Why is base64 encoding in a Kubernetes Secret NOT a real security measure?
3. Why might updating a ConfigMap not immediately affect a Pod that already consumed it as an environment variable?
Kubernetes