Baking configuration and credentials directly into a container image means rebuilding the image every time a value changes, and it means secrets end up in your image layers. ConfigMaps and Secrets exist to separate configuration from the image itself.
ConfigMaps — for non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
FEATURE_FLAG_NEW_UI: "true"Secrets — for sensitive values
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
DATABASE_PASSWORD: cG9zdGdyZXNfcGFzc3dvcmQ= # base64-encoded# Easier than hand-encoding base64 yourself
kubectl create secret generic app-secrets \
--from-literal=DATABASE_PASSWORD=postgres_passwordUsing either as environment variables
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secretsenvFrom injects every key from the ConfigMap/Secret as an environment variable in one shot — useful when you have many values and don't want to list each individually.
Or mounted as files
spec:
containers:
- name: app
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: app-configEach key becomes a file in the mounted directory, with the value as its contents — the right choice for applications that expect configuration as files (like a .env file or an nginx config) rather than environment variables, or for large values environment variables aren't well-suited to.
The important caveat: Secrets aren't encrypted by default
kubectl get secret app-secrets -o yaml shows the value base64-encoded, not encrypted — base64 is an encoding, trivially reversible (echo "..." | base64 -d), not a security measure. Anyone with kubectl access to read Secret objects in that namespace can decode them instantly.
Real protection requires layering on top of the base Secret object:
- Encryption at rest — configuring the cluster's API server with an encryption provider so Secrets are actually encrypted in etcd, not just base64-encoded.
- RBAC — restricting who can read Secret objects via Kubernetes' role-based access control, so "base64 isn't encryption" matters less if far fewer people/services can read the object in the first place.
- External secret managers — tools like AWS Secrets Manager, HashiCorp Vault, or Google Secret Manager integrated via an operator (e.g., External Secrets Operator), so the actual sensitive value lives in a purpose-built secret store, and Kubernetes only holds a reference or a short-lived synced copy.
A reasonable default approach
For low-sensitivity config: ConfigMaps, without a second thought. For genuinely sensitive values (database credentials, API keys, tokens): native Secrets are fine for getting started, but as soon as multiple people have cluster access or the values are genuinely high-stakes, layer on RBAC restrictions and consider an external secret manager rather than treating Secrets as sufficient protection on their own.