Provide config with ConfigMaps and Secrets
A Kubernetes Secret is not encrypted by default; its values are only base64-encoded. Anyone who can read the Secret object, or the underlying etcd datastore, can recover the plaintext with a single base64 -d. Everything else on this page follows from that limitation.
Warning
Gate Secrets with RBAC so only the ServiceAccounts that need a value can read it, and encrypt etcd at rest so the plaintext is not sitting in the datastore for anyone with disk or backup access.
ConfigMap or Secret
Both objects hold key-value data; the difference is intent. A ConfigMap carries non-sensitive config: feature flags, URLs, a whole config file. A Secret carries values you would not want stored unencrypted: passwords, tokens, keys. The Secret is the path with RBAC gating and etcd encryption behind it, so choose it whenever the value is sensitive.
Inject as env vars or files
# Inject as environment variables
envFrom:
- configMapRef:
name: app-config # every key in the ConfigMap becomes a var
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-creds # pull one Secret key into one named var
key: password
# Or mount them as files
volumes:
- name: config
configMap:
name: app-config
volumeMounts:
- name: config
mountPath: /etc/app # each key lands as a file under this path
Environment variables are the simplest option, but they have one limitation: their values are fixed for the lifetime of the pod and only change when it restarts. A volume-mounted ConfigMap, by contrast, updates live after you edit it (following a short propagation delay). If your application rereads its config file at runtime, mount the config as a volume rather than injecting it as environment variables.
One pitfall: subPath mounts a single key as one file without shadowing the rest of the directory, exactly what you use first. It also disables live updates, a common reason a config change never takes effect. Check for a subPath mount first.
Immutable ConfigMaps and Secrets
Set immutable: true when the content must not change under a running application. The API server then refuses edits, which stops accidental updates, and the kubelet stops watching the object entirely, which is real load off the control plane on a cluster with thousands of pods. To change one, create a new object under a new name and roll the workload onto it.
Keeping secrets out of Git
Because you deploy from Git, a raw Secret cannot go in the repo, or the credential stays in the history permanently. Two patterns keep GitOps intact:
- Sealed Secrets encrypts the value against a key that lives only in the cluster. The blob is safe to commit; a controller unseals it in place. Rotate that key and old sealed values stop decrypting, so plan for it.
- External Secrets keeps the value in an external manager and commits only a pointer, so Git never touches the plaintext.
Sealed Secrets suits a self-contained cluster; External Secrets suits organizations already running a central vault. Pick one before the first manifest lands in Git; retrofitting means rewriting history. See Deploy apps from Git with Argo CD .
To confirm it landed, kubectl exec <pod> -- printenv DB_PASSWORD. A pod that never starts its container usually means a wrong key in the secretKeyRef, or a Secret in a different namespace than the pod: Secrets do not cross namespaces.
Set security contexts on a workload
Where security contexts fit in the workload-hardening ladder, with a pointer to the security page that owns the fields, the restricted example, and Pod Security Standards enforcement.
Pull images from a private registry
Give pods credentials to pull from a private container registry, per namespace or per service account, and read your way out of ImagePullBackOff.