05 - ConfigMaps and Secrets¶
What this session is¶
About 45 minutes. How to inject configuration into pods without baking it into the image - ConfigMaps (non-sensitive) and Secrets (sensitive).
The problem¶
You build an image once and want to deploy it in dev, staging, prod - each with different config (different database URLs, API keys, log levels). Hardcoding in the Dockerfile breaks that. Hardcoding in the Deployment YAML scatters config across many places.
ConfigMap stores key-value config. Secret stores sensitive key-value config (passwords, API keys, certs). You reference them from Deployments.
ConfigMap¶
Create from a YAML file:
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: info
FEATURE_FLAG_A: "true"
app.properties: |
server.port=8080
timeout=30s
Apply:
Alternatively, create from a file or literals:
kubectl create configmap app-config --from-file=app.properties
kubectl create configmap app-config --from-literal=LOG_LEVEL=info --from-literal=PORT=8080
The YAML form is preferred for version-controlled config.
Use a ConfigMap in a Deployment¶
Two ways: as env vars, or mounted as files.
As env vars:
spec:
containers:
- name: app
image: my-app:1.0
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
Or import all keys at once:
As mounted files:
spec:
containers:
- name: app
image: my-app:1.0
volumeMounts:
- name: config-vol
mountPath: /etc/app
volumes:
- name: config-vol
configMap:
name: app-config
Each key in the ConfigMap becomes a file. So app.properties is at /etc/app/app.properties and LOG_LEVEL is at /etc/app/LOG_LEVEL.
For an app that reads config from a file (like nginx), mount form is the way.
Secret¶
Same shape, but for sensitive data:
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
stringData:
DATABASE_PASSWORD: hunter2
API_KEY: xyzzy
stringData accepts plain strings (Kubernetes base64-encodes them internally). The alternative data: field requires you to base64-encode yourself - annoying; prefer stringData.
Apply:
Note: when you view a Secret with -o yaml, values appear base64-encoded. Decode:
Use Secrets in pods the same way as ConfigMaps:
Or mount as files. For TLS certs, mounting is the common pattern.
Secrets are not actually that secret¶
Important caveat: Kubernetes Secrets are not encrypted at rest by default. They're base64-encoded (which is encoding, not encryption - trivially decoded). Anyone with API access to your cluster can read them.
What this means: - For local development: Secrets are fine. - For production: enable encryption at rest (kube-apiserver flag) AND use RBAC to limit who can read Secrets, AND/OR use external secret managers (HashiCorp Vault, AWS Secrets Manager, sealed-secrets, External Secrets Operator).
Treat Kubernetes Secrets as "values I prefer not to log" - not "values I can keep secret from attackers who own the cluster."
A full example: app with config and secret¶
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: debug
PORT: "8080"
---
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
stringData:
DATABASE_PASSWORD: secret-pass
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 2
selector:
matchLabels: {app: app}
template:
metadata:
labels: {app: app}
spec:
containers:
- name: app
image: my-app:1.0
envFrom:
- configMapRef:
name: app-config
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret
key: DATABASE_PASSWORD
ports:
- containerPort: 8080
The --- separates multiple YAML documents in one file. kubectl apply -f handles all three at once.
Updating ConfigMaps and Secrets¶
If you edit a ConfigMap and re-apply: - For env-injected values: the pods do NOT pick up the new values until they're restarted. (Env vars are set at container start.) - For mounted files: the kubelet updates the files within ~minutes. The app needs to re-read them (or use a config watcher).
So always restart Deployments after config changes:
That triggers a rolling restart that picks up new env values. Pair every ConfigMap/Secret update with a rollout restart for the apps that use them.
Exercise¶
-
Apply the full example above (three resources in one file). Check
kubectl get cm,secret,deploy. -
Inspect:
Should show your config + secret values. -
Update the ConfigMap: Edit
Still shows the old value. Restart: Now shows new value.LOG_LEVEL: info. Apply. -
Mount as file: Edit the Deployment to mount the ConfigMap as
/etc/config/instead of env. Apply, restart, exec,ls /etc/config/. Each key is a file.
What you might wonder¶
"Can I have multiple ConfigMaps for one app?"
Yes - list multiple configMapRef or secretRef under envFrom. Useful for splitting "app config" from "feature flags."
"What about typed secrets (TLS, dockerconfigjson)?"
Kubernetes has specific types: kubernetes.io/tls for TLS certs, kubernetes.io/dockerconfigjson for image-pull credentials. The type: field distinguishes. Functionally still key-value; the types signal intent and let tools handle them specially.
"Sealed Secrets? External Secrets?"
Sealed Secrets (bitnami-labs/sealed-secrets) encrypts a Secret to a public key so it's safe to commit to git. External Secrets Operator pulls from external secret managers and creates K8s Secrets dynamically. Both for production use.
Done¶
- Create ConfigMaps from YAML, files, or literals.
- Create Secrets (using
stringData). - Inject as env vars (
env,envFrom). - Mount as files (
volumeMounts+volumes). - Understand the "restart on config change" pattern.
- Know Secrets aren't really secret without extra work.