Frontend Labs
⋯

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

  1. What's the practical difference between a ConfigMap and a Secret, given that both are, by default, unencrypted at rest?
  2. Why is "Kubernetes Secrets are base64, not encrypted" such a commonly real, commonly misunderstood point?
  3. 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.

Generated from real-life-os-guide.md · 8 Oct 2026 16:36