10.43 - Config and Secrets

Goal of the day: no configuration baked into images. Everything your app needs (env vars, files, credentials) comes from the cluster at runtime.

ConfigMap: non-secret config

ConfigMaps hold strings (or files). Two consumption modes:

  1. As env vars (snapshot at pod creation; changing the ConfigMap does NOT update running pods).
  2. As mounted files (live: kubelet updates the file on change; the app must re-read it to notice).

Env vars

apiVersion: v1
kind: ConfigMap
metadata: { name: app-config }
data:
  LOG_LEVEL: info
  PORT: "8080"
---
# in a pod spec:
env:
  - name: LOG_LEVEL
    valueFrom:
      configMapKeyRef: { name: app-config, key: LOG_LEVEL }
  - name: PORT
    valueFrom:
      configMapKeyRef: { name: app-config, key: PORT }

Mounted files

# in a pod spec:
volumes:
  - name: config
    configMap: { name: app-config }
containers:
  - volumeMounts:
      - { name: config, mountPath: /etc/myapp }

Each key becomes a file: /etc/myapp/LOG_LEVEL, /etc/myapp/PORT. Perfect for config files (nginx.conf, application.properties) and dotenv-style loading.

Imperative shortcuts

kubectl create configmap app-config --from-literal=LOG_LEVEL=info
kubectl create configmap nginx-conf --from-file=nginx.conf=./nginx.conf
kubectl create configmap dir-conf --from-file=./conf.d/   # whole dir
kubectl get configmap app-config -o yaml

Secret: config that must not be readable by everyone

Secrets are the same mechanism with one difference: they are meant for credentials, and (in a real cluster) can be encrypted at rest and gated by RBAC. IMPORTANT: the data you see in kubectl get secret -o yaml is base64, which is ENCODING, not ENCRYPTION. Anyone who can read the Secret can decode it.

kubectl create secret generic dbpass --from-literal=password=supersecret
kubectl get secret dbpass -o yaml
#   data:
#     password: c3VwZXJzZWNyZXQ=        <- base64 of "supersecret"
echo c3VwZXJzZWNyZXQ= | base64 -d      # supersecret

In manifests, use stringData so the file stays human-readable; the API server encodes it to data for you:

apiVersion: v1
kind: Secret
metadata: { name: dbpass }
type: Opaque
stringData:
  password: supersecret

Using a Secret

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef: { name: dbpass, key: password }

Or as files (mountPath /etc/db, file password). This is how you hand config files with embedded credentials to an app.

Secrets in k3s

  • Stored in the cluster DB (SQLite by default), NOT encrypted at rest by default. In etcd-backed clusters you can enable encryption at rest; k3s supports it via --secrets-encryption.
  • Anything a pod can read, a pod can leak: the value ends up in the pod’s env or filesystem. Logs and kubectl exec output can exfiltrate it. Treat Secrets as “protected from casual reading”, not as unbreakable.
  • Better pattern for truly sensitive things: an external vault (SOPS + age for files, or a secrets operator). Note it for 11-next-steps.

The workflow that avoids the “config in image” trap

  1. Build the image with NO config baked in (defaults only).
  2. Create ConfigMap(s) for anything non-secret.
  3. Create Secret(s) for credentials (via kubectl create secret, or stringData in a manifest file you keep OUT of git).
  4. Wire both in the Deployment manifest (env + mounts).
  5. Roll out config changes by re-applying the ConfigMap/Secret and doing a rollout restart if env-var style: kubectl rollout restart deployment/app.

Gotchas

  • Env-var injection is a SNAPSHOT. Update ConfigMap -> env unchanged until pod recreation. Files via volumeMount DO update live (kubelet syncs every ~1 min by default). Design accordingly.
  • ConfigMap keys must be valid env var names when used as env (no dashes, no dots). As files, anything goes.
  • A ConfigMap or Secret that does not exist -> pod stuck CreateContainerConfigError. kubectl describe pod shows the exact missing key. Fix the name/key, not the pod.
  • Secrets are namespaced. A Secret in lab is NOT visible from blog.
  • kubectl apply -f on a Secret keeps the raw value in your shell history / file. Use --from-literal sparingly and keep Secret manifests out of version control, or use sealed-secrets/SOPS later.

Challenge wrap-up

C10: env injection. C11: file mount. C12: base64 decoding + the “encoding not encryption” answer. If you can do all three without the cheatsheet, you have day 4 down.


This site uses Just the Docs, a documentation theme for Jekyll.