Chapter 57 — Configuration: ConfigMaps, Secrets, and environment
In one sentence
ConfigMaps hold non-sensitive configuration and Secrets hold sensitive values, both stored separately from your container image so the SAME image can run with different configuration in different environments — but Secrets being base64-ENCODED, not encrypted, by default is a real, commonly misunderstood security detail.
Why this matters
Baking configuration (especially credentials) directly into a container image means rebuilding the image for every environment, and — worse — that image now contains a secret anyone who can pull it can read (Chapter 52).
How it actually works
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
API_TIMEOUT: "30s"
---
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
stringData: # plaintext HERE, in the manifest --
DB_PASSWORD: "supersecret123" # Kubernetes base64-ENCODES it on
# storage (NOT encryption, Chapter
# 6 of the advanced security guide
# in this wiki covers this
# precisely)
# using both in a pod spec
spec:
containers:
- name: app
image: myapp:1.0
envFrom:
- configMapRef:
name: app-config # every key becomes an env var
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret
key: DB_PASSWORD
kubectl create configmap app-config --from-literal=LOG_LEVEL=info
kubectl get configmap app-config -o yaml
kubectl create secret generic app-secret --from-literal=DB_PASSWORD=supersecret123
kubectl get secret app-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
# ^ trivially decoded -- this is exactly the point: it's NOT
# encryption, it's encoding, exactly G1's Chapter 6 lesson
# ("Base64 is an envelope, not a safe"), applied directly to
# Kubernetes Secrets
Real-world example
This wiki's own advanced security guide (`security/real-life-
security-guide-v1.md`, Part 4 Chapter 16) documents this EXACT,
genuinely common, real misunderstanding at length: Kubernetes
Secrets are, BY DEFAULT, stored in etcd (Chapter 54's source of
truth) as PLAIN BASE64 -- not encrypted -- unless the cluster
operator EXPLICITLY configures encryption-at-rest with a KMS/AESGCM
provider. Anyone who can read etcd directly (or an etcd BACKUP, or a
snapshot in an over-permissioned storage bucket) gets every "Secret"
in the cluster in effectively plaintext, with one trivial `base64
-d` step.
This is a genuinely, repeatedly real source of security incidents
and misconfigurations: engineers reasonably ASSUME "Secret," as a
Kubernetes object TYPE, implies real encryption, precisely because
the name strongly suggests it -- and the base64 encoding step, which
IS applied, LOOKS superficially like protection to someone who
doesn't already know Chapter 6's "encoding is not encryption"
distinction cold. The practical, real fix -- covered in full
operational depth in this wiki's advanced security guide -- is
configuring genuine etcd encryption-at-rest, AND treating Kubernetes
Secrets as one layer of a real secrets-management strategy (ideally
backed by a proper external secrets manager, G1 Chapter 49) rather
than the whole story on their own.
Try it yourself
kubectl create configmap demo-config --from-literal=GREETING="hello from a ConfigMap"
kubectl create secret generic demo-secret --from-literal=PASSWORD="not-really-secret-until-encrypted"
# See the "encoding, not encryption" lesson directly
kubectl get secret demo-secret -o jsonpath='{.data.PASSWORD}'
echo # (that's the base64-encoded value)
kubectl get secret demo-secret -o jsonpath='{.data.PASSWORD}' | base64 -d
echo # trivially recovered -- exactly this chapter's point
cat > configpod.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: config-demo
spec:
containers:
- name: demo
image: alpine
command: ["sh", "-c", "echo GREETING=$GREETING; echo PASSWORD=$PASSWORD; sleep 300"]
env:
- name: GREETING
valueFrom:
configMapKeyRef:
name: demo-config
key: GREETING
- name: PASSWORD
valueFrom:
secretKeyRef:
name: demo-secret
key: PASSWORD
EOF
kubectl apply -f configpod.yaml
sleep 2
kubectl logs config-demo
kubectl delete -f configpod.yaml
kubectl delete configmap demo-config
kubectl delete secret demo-secret
Common mistakes
- Treating a Kubernetes Secret as inherently encrypted. It's base64 ENCODED by default — genuinely, commonly misunderstood, and a real security gap unless you explicitly configure etcd encryption-at-rest.
- Baking configuration or credentials directly into a container image. Means rebuilding the image per environment, and a credential leak the moment anyone pulls the image (Chapter 52).
- Putting genuinely sensitive values in a ConfigMap instead of a Secret. ConfigMaps get NO special treatment at all — always use a Secret (and real encryption-at-rest) for anything sensitive.
Check yourself
- What's the practical difference between a ConfigMap and a Secret, given that both are, by default, unencrypted at rest?
- Why is "Kubernetes Secrets are base64, not encrypted" such a commonly real, commonly misunderstood point?
- What's the actual, complete fix for storing genuinely sensitive values safely in Kubernetes?
Further reading
- Docs: Kubernetes' own official "Secrets" documentation, specifically the "Risks" section — refreshingly honest about this exact limitation.
- Guide: this wiki's advanced security guide, Part 4 Chapter 16 ("Kubernetes architecture and its trust boundaries") — the full, real operational treatment of this exact topic.
- Reference:
kubectl explain secret,kubectl explain configmap.