19

ConfigMap and Secret

Video: Day 19/40 — Kubernetes ConfigMap and Secret • https://www.youtube.com/watch?v=Q9fHJLSyd7Q • Duration: ~17 min

Key terms

TermMeaning
ConfigMapNon-secret config stored as key/values
SecretBase64-encoded sensitive data
env / envFromInject values as environment variables
Volume mountExpose config/secret data as files
stringDataPlain-text secret input that Kubernetes encodes
DecouplingKeeping configuration out of the image

Problem & solution

Baking config and credentials into images makes them environment-specific and leaks secrets into the build. The same image should run across dev/stage/prod with configuration injected at runtime.

Solution: Externalize config into ConfigMaps and sensitive data into Secrets, consumed as env vars or mounted files, with encryption at rest for Secrets.

The analogy

Every ship docking at our port needs two kinds of instructions. The routine stuff, which lane to use and what flags to fly, is chalked on a posted instructions board anyone walking past can read. The sensitive stuff, the cargo combination and the captain's orders, is written on a sheet locked in the harbour-master's safe and handed only to crew who need it. Kubernetes splits configuration the exact same way: a ConfigMap is the posted board for non-secret settings, while a Secret is the sealed orders in the safe for passwords, tokens, and keys, and both are read by the same ship's crew doing the work.

Where this fits in the cluster

ConfigMaps and Secrets are cluster objects stored in etcd. The kubelet on a node injects them into a container as env vars or mounted files at runtime.

The idea: decouple config from image

Don't bake config/credentials into images. Inject them at runtime so the same image runs across dev/staging/prod.

ConfigMap vs Secret

Both hold key/value config, but a ConfigMap is for non-sensitive settings while a Secret is for sensitive data and is handled more carefully.

   ConfigMap -> plain config (URLs, flags, ports). NOT secret.
   Secret    -> sensitive data (passwords, tokens, keys).
                stored base64-encoded; can be encrypted at rest.

base64 is encoding, not encryption — anyone can decode it. Protect Secrets with RBAC and encryption-at-rest.

Two ways to consume them

A pod can read a ConfigMap or Secret either as environment variables or as files mounted into the container.

   (A) Environment variables          (B) Mounted as files (volume)
   container env:                      /etc/config/
     APP_COLOR=blue                       app_color   -> "blue"
     DB_PASS=*****                         db_pass     -> "*****"
   - simple, static at start          - updates can propagate to files

Create a ConfigMap

You can build a ConfigMap imperatively from literal key/values or from a file.

# from literals
kubectl create configmap app-config \
  --from-literal=APP_COLOR=blue --from-literal=APP_MODE=prod

# from a file
kubectl create configmap app-config --from-file=app.properties

kubectl get configmap app-config -o yaml

ConfigMap YAML:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_COLOR: blue
  APP_MODE: prod

Create a Secret

A Secret is created the same way, but Kubernetes stores its values base64-encoded rather than as plain text.

kubectl create secret generic db-secret \
  --from-literal=DB_USER=admin --from-literal=DB_PASS=s3cr3t

kubectl get secret db-secret -o yaml      # values are base64
echo 'czNjcjN0' | base64 -d              # decode example

Secret YAML (values base64-encoded):

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  DB_USER: YWRtaW4=        # echo -n admin | base64
  DB_PASS: czNjcjN0        # echo -n s3cr3t | base64
# (or use stringData: for plain text that K8s encodes for you)

Consume as ENV VARS

Inject every key at once with envFrom, or map a single key into a named variable with valueFrom.

spec:
  containers:
    - name: app
      image: myapp
      envFrom:
        - configMapRef: { name: app-config }   # all keys as env
        - secretRef: { name: db-secret }
      # or pick one key:
      env:
        - name: APP_COLOR
          valueFrom:
            configMapKeyRef: { name: app-config, key: APP_COLOR }
        - name: DB_PASS
          valueFrom:
            secretKeyRef: { name: db-secret, key: DB_PASS }

Consume as MOUNTED FILES

Mounting as a volume turns each key into a file under the mount path, which lets updates propagate without restarting the pod.

spec:
  containers:
    - name: app
      image: myapp
      volumeMounts:
        - name: cfg
          mountPath: /etc/config
        - name: sec
          mountPath: /etc/secret
          readOnly: true
  volumes:
    - name: cfg
      configMap: { name: app-config }
    - name: sec
      secret: { secretName: db-secret }

The result on disk is one file per key, named after the key and holding its value:

   /etc/config/APP_COLOR   -> file containing "blue"
   /etc/secret/DB_PASS     -> file containing "s3cr3t"

env vs volume (update behavior)

The two consumption styles differ when the config changes: env vars are frozen at start, while mounted files can refresh live.

   env var:  fixed at container start -> change needs pod restart
   volume:   mounted files can refresh after a ConfigMap/Secret update
             (with some kubelet sync delay)

End-to-end example: config as env + secret as file

One pod that reads non-secret settings as env vars from a ConfigMap and a sensitive password as a mounted file from a Secret.

apiVersion: v1
kind: ConfigMap
metadata: { name: app-config }
data: { APP_COLOR: blue, APP_MODE: prod }
---
apiVersion: v1
kind: Secret
metadata: { name: db-secret }
type: Opaque
stringData: { DB_PASS: s3cr3t }     # stringData: K8s base64-encodes for you
---
apiVersion: v1
kind: Pod
metadata: { name: app-e2e }
spec:
  containers:
    - name: app
      image: busybox
      command: ['sh','-c','echo color=$APP_COLOR; cat /etc/secret/DB_PASS; sleep 3600']
      envFrom:
        - configMapRef: { name: app-config }      # all keys as env vars
      volumeMounts:
        - { name: sec, mountPath: /etc/secret, readOnly: true }
  volumes:
    - name: sec
      secret: { secretName: db-secret }

Apply it, then confirm both paths arrive: the env vars come from the ConfigMap and the password shows up as a mounted file.

kubectl apply -f app-e2e.yaml
kubectl logs app-e2e                         # color=blue + the secret value
kubectl exec app-e2e -- env | grep APP_       # env vars from the ConfigMap
kubectl exec app-e2e -- cat /etc/secret/DB_PASS   # secret mounted as a file

End-to-end flow

Config and secrets live in etcd and the kubelet injects them into the container as env vars or mounted files.

Key takeaways

  • ConfigMap = non-secret config; Secret = sensitive data (base64, not encrypted).
  • Inject via env vars (static) or mounted files (can refresh).
  • Same image + different ConfigMaps/Secrets = portable across environments.

Checklist

  • [ ] Created a ConfigMap and a Secret (literals + file)
  • [ ] Injected them as env vars (envFrom and single-key)
  • [ ] Mounted them as files and read the values
  • [ ] Decoded a Secret value with base64 and understand it's not encryption