Keep configuration out of the image so one image runs in dev and prod.
flowchart LR IMG["image app:1.0"] --> P1["dev pod"] IMG --> P2["prod pod"] CM1["ConfigMap dev"] --> P1 CM2["ConfigMap prod"] --> P2 S["Secret db-cred"] --> P2
- ConfigMap: non-sensitive key-values (log level, URLs, config files).
- Secret: sensitive data (passwords, tokens, certificates). Values are stored as base64 — only a character encoding; anyone who can read the Secret can decode it. Real protection needs encryption at rest for etcd, tight RBAC, and never committing Secret YAML to git.
- The
kubernetes.io/dockerconfigjsontype is used asimagePullSecretsto pull private images.
kubectl create configmap app-config --from-literal=LOG_LEVEL=debug --from-file=app.properties -n dev
kubectl create secret generic db-cred --from-literal=password=changeme-example -n dev
kubectl get secret db-cred -n dev -o jsonpath='{.data.password}' | base64 -d
envFrom:
- configMapRef: { name: app-config }
env:
- name: DB_PASSWORD
valueFrom: { secretKeyRef: { name: db-cred, key: password } }
Updates
- Environment variables are read when the container starts: change a ConfigMap and pods using
envdo not change until restarted (kubectl rollout restart deployment/app). - When mounted as a volume, the kubelet updates the files after a short delay (unless
subPathis used).
| Command | Meaning |
|---|---|
kubectl get cm,secret -n dev |
List ConfigMaps and Secrets |
kubectl describe secret db-cred -n dev |
Shows only key names and sizes, not values |
Mounting configuration as files
volumes:
- name: config
configMap:
name: app-config
items:
- { key: app.properties, path: application.properties }
containers:
- name: app
volumeMounts: [{ name: config, mountPath: /etc/app, readOnly: true }]
Each key becomes a file. Files suit whole config documents (nginx.conf, application.yaml); environment variables suit single values. A ConfigMap or Secret is limited to 1 MiB.
Making changes predictable
- Immutable ConfigMaps and Secrets (
immutable: true) cannot be edited, protecting against accidental changes and reducing apiserver load. To change one, create a new name and update the pod template. - Putting a hash of the configuration in a pod annotation (Helm and Kustomize do this) means every config change triggers a rolling update, so pods never run with stale settings.
Secret types
| Type | Holds |
|---|---|
Opaque |
Arbitrary key-values (the default for generic) |
kubernetes.io/tls |
tls.crt and tls.key for Ingress or apps |
kubernetes.io/dockerconfigjson |
Registry credentials for imagePullSecrets |
kubernetes.io/service-account-token |
Legacy long-lived ServiceAccount token |
kubectl create secret tls web-tls --cert=tls.crt --key=tls.key builds the TLS type directly.
Keeping secrets out of Git
| Approach | How it works |
|---|---|
| Sealed Secrets | Encrypt with the cluster's public key; only the in-cluster controller can decrypt, so the sealed file can be committed |
| External Secrets Operator | Syncs values from AWS Secrets Manager, Azure Key Vault, Vault, and others into Kubernetes Secrets |
| Secrets Store CSI driver | Mounts secrets from an external store straight into pods as files |
| SOPS | Encrypts values in YAML files with cloud KMS keys before committing |
Limit who can get Secrets with RBAC; anyone allowed to create pods in a namespace can also read its Secrets by mounting them, so namespace access matters too.