Configuration
Your app needs a database URL. You hardcode it inside the image.
Wrong endpoint in staging. Wrong credentials in production. You rebuild the image every time config changes.
The image should not know which environment it runs in. Configuration belongs outside the container.
Kubernetes has two objects for this: ConfigMap and Secret.
ConfigMap vs Secret
A ConfigMap stores non-sensitive configuration: feature flags, URLs, mode settings, log levels.
A Secret stores sensitive data: passwords, tokens, certificates. Values are base64-encoded in the API. Access is controlled separately from ConfigMaps.
The mechanics of using them are identical. The difference is intent and access control.
Do not store passwords in ConfigMaps. Secrets can be encrypted at rest if your cluster is configured for it. ConfigMaps cannot.
Two ways to inject configuration
Environment variables — values are injected at pod startup. Simple and visible via env. Cannot update without restarting the pod.
Volume mounts — the ConfigMap or Secret is mounted as files inside the container. Files update automatically when the ConfigMap changes, without restarting the pod.
Use environment variables for simple values that change infrequently. Use volume mounts for config files or anything that needs live updates.
Hands-on
Fork eigenbytes-devops-labs — manifests for this lab are in 04-kubernetes/configuration/: configmap-demo.yaml, secret-demo.yaml, and vol-demo.yaml.
Create a ConfigMap
kubectl create configmap app-config \
--from-literal=APP_MODE=debug \
--from-literal=LOG_LEVEL=info
kubectl get configmap app-config -o yaml
data:
APP_MODE: debug
LOG_LEVEL: info
Create a Secret
kubectl create secret generic app-secret \
--from-literal=DB_PASSWORD=admin123
kubectl get secret app-secret -o yaml
data:
DB_PASSWORD: YWRtaW4xMjM= # base64-encoded
Decode it:
echo "YWRtaW4xMjM=" | base64 --decode
# admin123
Inject as environment variables
kubectl run env-demo \
--image=busybox \
--restart=Never \
--env-from=configmap/app-config \
--env-from=secret/app-secret \
-- sh -c 'env | grep -E "APP_MODE|LOG_LEVEL|DB_PASSWORD"'
APP_MODE=debug
LOG_LEVEL=info
DB_PASSWORD=admin123
Inject as a volume mount
Save this as vol-demo.yaml:
apiVersion: v1
kind: Pod
metadata:
name: vol-demo
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "cat /config/APP_MODE; sleep 3600"]
volumeMounts:
- name: config-vol
mountPath: /config
volumes:
- name: config-vol
configMap:
name: app-config
restartPolicy: Never
kubectl apply -f vol-demo.yaml
kubectl logs vol-demo
# debug
Watch the live update
kubectl patch configmap app-config -p '{"data":{"APP_MODE":"prod"}}'
kubectl exec vol-demo -- cat /config/APP_MODE
# prod
The file updated inside the running pod. No restart. No redeploy.
Now check the env-demo approach — those environment variables did not change. They are frozen from pod startup.
Cleanup
kubectl delete pod env-demo vol-demo --ignore-not-found
kubectl delete configmap app-config
kubectl delete secret app-secret
Quick reference
kubectl create configmap <name> --from-literal=KEY=VALUE
kubectl create configmap <name> --from-file=app.conf
kubectl create secret generic <name> --from-literal=KEY=VALUE
kubectl get configmaps
kubectl get secrets
kubectl describe configmap <name>
kubectl edit configmap <name>
kubectl delete configmap <name>
Lab files: eigenbytes-devops-labs/04-kubernetes/configuration