ConfigMap and Secret: Separating Configuration from the Image
A good image is immutable and usable everywhere: the same nginx image, or the same image of your app, must run on the dev machine, on staging, on production. That's only feasible if the configuration (database URL, feature flags, passwords) is not in the image but injected at runtime. Kubernetes gives you two tools: ConfigMap for ordinary configuration, Secret for sensitive data.
Why not bake configuration into the image
If you build the DB password or backend URL straight into the image, you'll have to rebuild the image for each environment — losing the whole point of containers. Worse, a password sitting in an image is a security risk (anyone who pulls the image can read it). The principle (in the spirit of the 12-factor app): separate configuration from code. In Kubernetes, ConfigMap and Secret are where that separated configuration lives.
image (immutable) + ConfigMap/Secret (per environment) = running pod
nginx:1.27 APP_MODE=production
DB_PASSWORD=...
ConfigMap: non-sensitive configuration
kubectl create configmap app-config \
--from-literal=APP_COLOR=blue \
--from-literal=APP_MODE=production
configmap/app-config created
A ConfigMap is just a bag of key=value pairs. Besides --from-literal, you can create one from a file (--from-file=config.properties) or declare it in YAML — the right approach for storing in Git.
Secret: sensitive data
kubectl create secret generic app-secret \
--from-literal=DB_PASSWORD='s3cr3t-p@ss'
secret/app-secret created
A Secret looks like a ConfigMap, but is reserved for passwords, tokens, keys. Kubernetes treats Secrets more carefully (doesn't print values to logs by default, can restrict access). But there's one thing you absolutely must know.
Warning: a Secret is just base64, NOT encryption
This is the most common and dangerous misconception about Kubernetes. View the value of the Secret you just created:
kubectl get secret app-secret -o jsonpath='{.data.DB_PASSWORD}'
czNjcjN0LXBAc3M=
Looks like it's "protected". But base64 is reversible encoding anyone can decode, not cryptography:
kubectl get secret app-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
s3cr3t-p@ss
The password shows up in plain sight. So:
- Don't commit Secret YAML into Git as if it were safe — base64 hides nothing.
- By default Secrets are stored in etcd unencrypted. Production needs to enable encryption at rest for etcd, and/or use an external solution like Sealed Secrets, External Secrets Operator, or your cloud provider's vault.
- The value of a "Secret" over a "ConfigMap" lies in how Kubernetes treats and authorizes it (RBAC, no logging), not in encrypting the content itself.
Keep this firmly in mind and you'll avoid a classic vulnerability.
Inject into a pod: two ways
A ConfigMap and Secret are useless if the pod can't read them. There are two ways to inject: as environment variables, or mounted as files. The pod below uses both:
apiVersion: v1
kind: Pod
metadata:
name: config-demo
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo COLOR=$APP_COLOR MODE=$APP_MODE; cat /etc/secret/DB_PASSWORD; sleep 3600"]
env: # way 1: ConfigMap → environment variable
- name: APP_COLOR
valueFrom:
configMapKeyRef: { name: app-config, key: APP_COLOR }
- name: APP_MODE
valueFrom:
configMapKeyRef: { name: app-config, key: APP_MODE }
volumeMounts: # way 2: Secret → file
- name: secret-vol
mountPath: /etc/secret
readOnly: true
volumes:
- name: secret-vol
secret:
secretName: app-secret
kubectl apply -f pod-config.yaml
kubectl logs config-demo
COLOR=blue MODE=production
s3cr3t-p@ss
Both routes succeed:
- Environment variables (
env.valueFrom.configMapKeyRef): the ConfigMap values become$APP_COLOR,$APP_MODEin the container. Good for simple configuration. (Inject a whole ConfigMap at once withenvFrom.) - Mounted as files (
volumes.secret+volumeMounts): each key of the Secret becomes a file in/etc/secret/. Good for config files, TLS certificates, or when you don't want the secret exposed via environment variables (env is more likely to get printed to logs).
When to use which? Short configuration → env. Config files/certificates, or secrets that need to stay quieter → mounted files. One convenience of mounting: if you update the ConfigMap, the mounted file updates along with it (env doesn't — you have to rebuild the pod).
Wrap-up
Separate configuration from the image so one image runs in every environment — that's the job of ConfigMap (ordinary configuration) and Secret (sensitive data). Both inject into a pod two ways: environment variables (configMapKeyRef/secretKeyRef, or envFrom) or mounted as files (via volumes). The most critical point: a Secret is just base64, not encryption — anyone with read access sees the value; production must enable encryption at rest for etcd and/or use a dedicated secret solution, and don't commit Secret YAML as if it were safe.
Configuration is now separated, but data inside the pod still evaporates with the pod — when the pod dies, it's all gone. Article 8: Volumes, PV and PVC — how to store durable data that outlives the pod's lifecycle.