1. ConfigMaps
A ConfigMap stores non-sensitive configuration as key-value pairs, decoupled from your Pod spec and your image entirely.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
FEATURE_NEW_CHECKOUT: "true"
MAX_UPLOAD_SIZE_MB: "25"
kubectl create configmap app-config \
--from-literal=LOG_LEVEL=info \
--from-literal=FEATURE_NEW_CHECKOUT=true
kubectl get configmap app-config -o yaml
Nothing here is a secret — this is exactly the kind of value that's fine to see in
a Git diff or a kubectl describe output: log levels, feature flags,
non-sensitive endpoint URLs, timeouts.
If yes, it's ConfigMap material. If leaking it would let someone impersonate your app, read your data, or bill your cloud account, it belongs in a Secret instead — which Section 3 covers honestly, including what a Secret does not protect against.
2. Mounting as Env Vars vs. Files
A ConfigMap can be consumed two ways — as individual environment variables, or as files inside a mounted volume. Each has a real tradeoff, not just a style preference:
spec:
containers:
- name: my-app
image: myrepo/my-app:1.5.0
envFrom:
- configMapRef:
name: app-config
# every key in app-config becomes an env var: LOG_LEVEL, FEATURE_NEW_CHECKOUT, etc.
spec:
containers:
- name: my-app
image: myrepo/my-app:1.5.0
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: app-config
# each key becomes a FILE at /etc/config/LOG_LEVEL, /etc/config/FEATURE_NEW_CHECKOUT, etc.
The key practical difference: environment variables are set once, at container start — updating the ConfigMap doesn't change a running Pod's env vars until it's restarted. A mounted file updates live (with a short propagation delay) without restarting the Pod, which matters for apps that watch a config file for changes.
A handful of flat key-value settings is a natural fit for env vars. A whole config file — nginx.conf, an application.yaml — is a natural fit for the file-mount approach, letting you keep the same config format your app already expects.
3. Secrets, Honestly
A Secret looks almost identical to a ConfigMap, but here's the part that surprises people the first time: by default, its values are base64-encoded, not encrypted.
echo -n "supersecretpassword" | base64
# c3VwZXJzZWNyZXRwYXNzd29yZA==
echo -n "c3VwZXJzZWNyZXRwYXNzd29yZA==" | base64 -d
# supersecretpassword <- anyone can reverse this instantly, no key required
kubectl get secret db-credentials -o yaml
# the "data" field shows base64 — readable by anyone with permission to read the Secret
So what does a Secret actually protect against, if not that? It keeps sensitive
values out of Pod specs and ConfigMaps (so they don't show up casually in
kubectl describe), and Kubernetes RBAC (Role-Based Access Control) can
restrict who's allowed to read Secret objects at all — that access control
is the real protection, not the base64 encoding itself.
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData: # "stringData" lets you write PLAIN text — Kubernetes
DB_PASSWORD: devpassword # base64-encodes it for you on apply, less error-prone
By default, Secrets sit in etcd unencrypted (etcd's own disk-level encryption aside). Enabling encryption-at-rest for Secrets specifically is a control-plane configuration step, and production clusters commonly go further with an external secrets manager (Vault, AWS Secrets Manager) that Kubernetes only references — genuinely out of scope for a local learning cluster, but important to know exists.
4. One Image, Many Environments
Put it together: the exact same Deployment manifest, referencing config and secrets by name, runs correctly in every environment just by swapping which ConfigMap and Secret exist under those names.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: myrepo/my-app:1.5.0 # SAME image tag, every environment
envFrom:
- configMapRef:
name: app-config # dev has one app-config, prod has another
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASSWORD
Dev, staging and prod can each define their own app-config and
db-credentials objects (typically in separate namespaces, previewed
properly in later weeks) — the Deployment manifest referencing them by name never
changes across environments, and neither does the image.
Managing separate ConfigMap/Secret YAML per environment by hand gets tedious fast — Week 13's Helm charts template exactly this, letting one chart produce different config per environment from a single set of values files.
5. Hands-on Exercise
Externalize your app's config, then run "two environments" from one image
Move hardcoded values out of your Deployment into a ConfigMap and Secret, then prove the same image can behave differently per environment without a rebuild.
Part 1 — Externalize:
- Pick your Week 8/9 app, and identify at least two config values it uses (a log level, a feature flag, a connection string) plus one sensitive value (a password or API key, even a fake one for this exercise).
- Create a ConfigMap for the non-sensitive values and a Secret for the sensitive one.
- Update your Deployment to consume both via
envFrom/env, and confirm withkubectl exec <pod> -- envthat the values actually appear inside a running container.
Changing a ConfigMap/Secret referenced via env vars won't affect an already-running Pod — you'll need to delete the Pods (or trigger a new rollout) after editing the ConfigMap for the new values to actually take effect, exactly as explained in Section 2.
Part 2 — Simulate two environments:
- Create a second ConfigMap with a different name (e.g.
app-config-prod) containing different values for the same keys — a different log level, a feature flag flipped the other way. - Update your Deployment's
configMapRefto point at the new ConfigMap name and reapply — confirm, via env vars inside a running Pod, that the behavior changed, without touching the image tag at all. - Run
kubectl get secret db-credentials -o yamland manually base64-decode the value shown in the output, to see firsthand exactly how little protection the encoding alone provides. - Write two sentences: one explaining what a Secret actually protects against, and one explaining what it does NOT protect against by default.
On macOS/Linux, decode with echo '<value>' | base64 -d; on Windows PowerShell, use [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("<value>")).
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Is a Kubernetes Secret's value encrypted by default?
Is a Kubernetes Secret's value encrypted by default?
No — by default it's base64-encoded, which is trivially reversible and provides no real confidentiality on its own. The actual protection comes from RBAC restricting who can read Secret objects, and optionally enabling encryption-at-rest at the control-plane level, or using an external secrets manager.
Q2
You update a ConfigMap that's mounted as environment variables on a running Pod. Does the Pod see the new values immediately?
You update a ConfigMap that's mounted as environment variables on a running Pod. Does the Pod see the new values immediately?
No. Environment variables are set once at container start — a running Pod keeps its original values until it's restarted or replaced. A ConfigMap mounted as a file, by contrast, updates live inside the Pod (with a short delay), which matters for apps that watch config files for changes.
Q3
What's a good rule of thumb for deciding ConfigMap vs. Secret for a given value?
What's a good rule of thumb for deciding ConfigMap vs. Secret for a given value?
Ask whether the value would be fine to see in a public GitHub repo. If yes (a log level, a feature flag), it belongs in a ConfigMap. If leaking it would let someone impersonate your app, access data, or incur cost on your behalf (a password, an API key), it belongs in a Secret.
Q4
Why does externalizing config into ConfigMaps/Secrets mean you never need to rebuild an image per environment?
Why does externalizing config into ConfigMaps/Secrets mean you never need to rebuild an image per environment?
The Deployment manifest references config and secrets by NAME, not by value — each environment can define its own ConfigMap/Secret under that same name with different values. The image itself, and the manifest referencing it, stay identical across dev, staging and prod; only what's mounted at runtime changes.