Hardcoding config values inside a container image is a bad idea. Every time a database URL or feature flag changes, we’d need to rebuild the image. Kubernetes solves this with ConfigMaps (for non-sensitive config) and Secrets (for sensitive stuff like passwords and API keys).
ConfigMaps
A ConfigMap holds key-value pairs of configuration data. We can create them in a few ways.
# From literal values
kubectl create configmap app-config \
--from-literal=APP_ENV=production \
--from-literal=LOG_LEVEL=info
# From a file
kubectl create configmap nginx-config --from-file=nginx.conf
# From an env file
kubectl create configmap app-config --from-env-file=.env
Or define it in YAML:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: "production"
LOG_LEVEL: "info"
DATABASE_HOST: "db-service"
Using ConfigMaps in Pods
We have two options: environment variables or mounted files.
As environment variables:
spec:
containers:
- name: app
image: my-app:latest
envFrom:
- configMapRef:
name: app-config # injects ALL keys as env vars
env:
- name: SPECIFIC_VAR # or pick specific keys
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
As mounted files:
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: config-volume
mountPath: /etc/config # each key becomes a file
volumes:
- name: config-volume
configMap:
name: app-config
With volume mounts, each key in the ConfigMap becomes a file in the mount path. So LOG_LEVEL becomes /etc/config/LOG_LEVEL with content info. The bonus? Volume-mounted ConfigMaps auto-update when the ConfigMap changes (with a small delay). Environment variables don’t — they need a Pod restart.
Secrets
Secrets work almost identically to ConfigMaps but are meant for sensitive data. The values are base64-encoded (not encrypted!).
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
DB_PASSWORD: cGFzc3dvcmQxMjM= # base64 of "password123"
DB_USER: YWRtaW4= # base64 of "admin"
# Easier: create from command line (handles encoding for us)
kubectl create secret generic db-credentials \
--from-literal=DB_PASSWORD=password123 \
--from-literal=DB_USER=admin
Using Secrets in Pods is the same pattern — secretRef instead of configMapRef:
envFrom:
- secretRef:
name: db-credentials
Important: Secrets Are NOT Encrypted by Default
This is a common interview gotcha. Kubernetes Secrets are only base64-encoded, which is not encryption. Anyone with kubectl get secret -o yaml access can decode them. To actually secure Secrets:
- Enable encryption at rest in etcd (
EncryptionConfiguration) - Use external secret managers like AWS Secrets Manager, HashiCorp Vault, or Sealed Secrets
- Limit access with RBAC (don’t let every developer read Secrets)
When to Use Which
- ConfigMap — non-sensitive config like log levels, feature flags, endpoint URLs, config files
- Secret — passwords, tokens, TLS certificates, API keys
Immutable ConfigMaps and Secrets
Since Kubernetes 1.21, we can mark a ConfigMap or Secret as immutable: true. Once set, it can’t be changed — only deleted and recreated. This is useful because:
- It protects against accidental updates
- It improves cluster performance (the API server skips watching immutable objects)
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v2
data:
APP_ENV: "production"
immutable: true
In simple language, ConfigMaps and Secrets let us keep config outside our container images. ConfigMaps for regular config, Secrets for sensitive data. Just remember that Secrets need extra steps to be truly secure.