Kubernetes ConfigMaps and Secrets: How to Manage App Config
Practice4 minInnovialabs editorial team
In short
Keep non-sensitive settings in ConfigMaps and credentials in Secrets, inject them as env vars or mounted files, and remember that base64 is encoding, not encryption — protect Secrets with RBAC, encryption at rest and tools like Sealed Secrets or External Secrets Operator.
The short answer
Kubernetes separates configuration from the container image with two objects:
- ConfigMap — non-sensitive settings: feature flags, URLs of other services, log level, config files.
- Secret — sensitive values: passwords, API tokens, TLS certificates.
Both are injected into pods in one of two ways: as environment variables or as files in a mounted volume. The same image then runs in dev, staging and production with different config.
The key caveat: a Secret's values are only base64-encoded. Anyone who can read the Secret object can decode it in one command. Real protection comes from access control, encryption at rest and keeping plain secrets out of Git.
Creating ConfigMaps and Secrets
You can create them imperatively for quick tests:
kubectl create configmap app-config --from-literal=LOG_LEVEL=info --from-file=app.yaml
kubectl create secret generic db-credentials --from-literal=DB_PASSWORD=change-me
For real projects, keep manifests declarative. In a Secret, stringData lets you write plain values; Kubernetes encodes them on save:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
API_BASE_URL: "https://api.internal"
---
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
DB_PASSWORD: "change-me"
Do not commit a manifest like the Secret above to Git — see the section on protecting secrets.
Env vars or mounted files
containers:
- name: app
image: registry.example.com/app:1.4.0
envFrom:
- configMapRef:
name: app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASSWORD
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
volumes:
- name: config
configMap:
name: app-config
| Environment variables | Mounted files | |
|---|---|---|
| Best for | Simple key-value settings | Whole config files, certificates |
| Updates when the object changes | No, only on pod restart | Yes, with a delay (not with subPath) |
| Risk | Can leak into logs, crash dumps, child processes | Readable by anything with file access in the container |
A practical rule: small settings as env vars, files and certificates as volumes, and prefer files for secrets when your app supports it.
Handling config reloads
Changing a ConfigMap does not restart anything. What happens depends on how you consume it:
- Env vars never change inside a running container. You need a new rollout.
- Mounted volumes are updated by the kubelet eventually, but the app must re-read the file. Many apps only read config at startup.
Common approaches:
- Roll the deployment after a change:
kubectl rollout restart deployment/app. - Add a checksum annotation to the pod template (Helm and Kustomize make this easy). When the config changes, the hash changes and Kubernetes rolls the pods automatically.
- Use immutable, versioned ConfigMaps (
app-config-v7,immutable: true) and point the deployment at the new name. Rollback becomes trivial. - Watch the file in the app if it must pick up changes without restarts.
Why base64 is not encryption
Base64 only makes binary data safe to store as text. Decoding needs no key:
kubectl get secret db-credentials -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
So the real safeguards are:
- RBAC: restrict
get,listandwatchon Secrets. Note thatlistreturns values too. - Encryption at rest in etcd (configured on the API server or enabled by your managed provider).
- No plain Secrets in Git, and no secrets baked into images.
- Limit who can create pods in a namespace: anyone who can run a pod there can mount its Secrets.
Getting secrets into Git safely
| Tool | How it works | Good fit |
|---|---|---|
| Sealed Secrets | You encrypt a Secret with the cluster's public key; only the in-cluster controller can decrypt it | GitOps teams without an external vault |
| External Secrets Operator | Syncs values from an external store (cloud secret managers, Vault) into Kubernetes Secrets | Teams that already use a central secret store |
| SOPS | Encrypts values in files with KMS or age keys; decrypted during deploy | Mixed tooling, Helm or Kustomize pipelines |
Common mistakes
- Putting passwords into a ConfigMap "temporarily".
- Committing base64-encoded Secrets and assuming they are hidden.
- Expecting env vars to update after editing a ConfigMap.
- Using
subPathmounts and wondering why files never refresh. - Giving CI or developers cluster-wide read access to Secrets.