Container images should be the same in every environment. What changes (database hosts, feature flags, API keys) belongs in configuration supplied at runtime. Kubernetes provides ConfigMaps for ordinary settings, and Secrets for sensitive values. Both can be injected as environment variables or mounted as files.
ConfigMaps
Create from literals or files:
kubectl create configmap web-config \
--from-literal=APP_ENV=production \
--from-literal=LOG_LEVEL=info \
--from-file=nginx.conf
Or declaratively:
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
data:
APP_ENV: production
LOG_LEVEL: info
app.yaml: |
features:
signup: true
Secrets
kubectl create secret generic web-secrets \
--from-literal=DB_PASSWORD='s3cr3t-value' \
--from-file=api-key=./api-key.txt
kubectl create secret tls web-tls --cert=tls.crt --key=tls.key
Important: Secret values are only base64-encoded, not encrypted. Anyone who can read Secrets in the namespace (or etcd backups) can decode them:
kubectl get secret web-secrets -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
So protect them (see below), and never commit Secret YAML with real values to Git.
Use them in a pod
As environment variables:
spec:
containers:
- name: web
image: myapp:1.8.3
envFrom:
- configMapRef: { name: web-config }
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef: { name: web-secrets, key: DB_PASSWORD }
As files:
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
- name: secrets
mountPath: /run/secrets/app
readOnly: true
volumes:
- name: config
configMap: { name: web-config }
- name: secrets
secret: { secretName: web-secrets, defaultMode: 0400 }
Each key becomes a file (/etc/app/app.yaml, /run/secrets/app/DB_PASSWORD). Files are generally preferred for secrets: they don’t show up in process environments, or crash dumps.
Updating configuration
- Mounted files update automatically (after a short delay), but your app must re-read them.
- Environment variables never change in a running pod. Restart it:
kubectl rollout restart deployment/web
A common pattern: put a hash of the config in a pod annotation (Helm does this with checksum/config), so any config change triggers a rolling update automatically. See Helm.
Protecting Secrets
- RBAC: limit who can
get/listSecrets. Many roles don’t need it. See Kubernetes security basics. - Encryption at rest for etcd (
EncryptionConfigurationon kubeadm clusters; k3s supports--secrets-encryption). - Keep real values out of Git, with one of:
- Sealed Secrets: encrypt with a cluster public key; safe to commit;
- SOPS (with age or GPG) + a GitOps tool;
- External Secrets Operator, syncing from Vault, a cloud secret manager, or Bitwarden Secrets Manager.
- Rotate secrets regularly, and immediately after a suspected leak.
See secrets management and Docker secrets.
Limits and tips
- ConfigMaps and Secrets are limited to 1 MiB each. Store large files elsewhere.
- Mark them
immutable: truefor config that never changes. It protects against accidental edits, and reduces API server load. - Namespaces scope them: a pod can only use ConfigMaps and Secrets in its own namespace.
Quick reference
| Task | Command |
|---|---|
| Create ConfigMap | kubectl create configmap x --from-literal=K=V |
| Create Secret | kubectl create secret generic x --from-literal=K=V |
| TLS secret | kubectl create secret tls x --cert … --key … |
| Decode a value | kubectl get secret x -o jsonpath='{.data.K}' | base64 -d |
| Apply env changes | kubectl rollout restart deploy/web |
| Git-safe secrets | Sealed Secrets / SOPS / External Secrets |
Related: Kubernetes deployments and services and kubectl cheat sheet.