Kubernetes ConfigMaps and Secrets: Manage App Configuration Safely
Learn how to use Kubernetes ConfigMaps and Secrets to manage application configuration and sensitive credentials. Covers creating, mounting, and updating config in Pods.

Hardcoding configuration into your Docker image is an anti-pattern — it means building a new image for every environment and baking credentials into version control. Kubernetes solves this with two purpose-built resources: ConfigMaps for non-sensitive configuration and Secrets for sensitive values like passwords and API keys.
ConfigMaps
A ConfigMap stores configuration as key-value pairs. The values can be simple strings or entire file contents. ConfigMaps are stored in etcd in plain text — use Secrets for anything sensitive.
Creating a ConfigMap
From literal values:
kubectl create configmap app-config \
--from-literal=NODE_ENV=production \
--from-literal=PORT=3000 \
--from-literal=LOG_LEVEL=infoFrom a YAML manifest (recommended):
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: default
data:
NODE_ENV: "production"
PORT: "3000"
LOG_LEVEL: "info"
DATABASE_HOST: "my-postgres-service"
DATABASE_PORT: "5432"kubectl apply -f configmap.yaml
kubectl get configmap app-config
kubectl describe configmap app-configUsing a ConfigMap: Environment Variables
Inject individual keys:
containers:
- name: api
image: myregistry/myapp:1.0
env:
- name: NODE_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: NODE_ENV
- name: PORT
valueFrom:
configMapKeyRef:
name: app-config
key: PORTOr inject all keys at once with envFrom:
containers:
- name: api
image: myregistry/myapp:1.0
envFrom:
- configMapRef:
name: app-configEvery key in app-config becomes an environment variable in the container.
Using a ConfigMap: Mounted as Files
If your app reads configuration from files (nginx.conf, application.yaml, etc.), mount the ConfigMap as a volume:
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
nginx.conf: |
server {
listen 80;
location / {
proxy_pass http://api-service:3000;
}
}containers:
- name: nginx
image: nginx:1.25-alpine
volumeMounts:
- name: nginx-conf-volume
mountPath: /etc/nginx/conf.d
readOnly: true
volumes:
- name: nginx-conf-volume
configMap:
name: nginx-configThe file nginx.conf appears at /etc/nginx/conf.d/nginx.conf inside the container.
Secrets
Secrets are like ConfigMaps, but designed for sensitive data. Values are base64-encoded at rest in etcd (not encrypted by default). Kubernetes also tries to avoid writing Secret values to logs.
Important: base64 encoding is not encryption. Enable Encryption at Rest in production and restrict access with RBAC.
Creating a Secret
From literal values:
kubectl create secret generic db-credentials \
--from-literal=POSTGRES_USER=myuser \
--from-literal=POSTGRES_PASSWORD=supersecretpassword \
--from-literal=POSTGRES_DB=myappFrom a YAML manifest using stringData (Kubernetes base64-encodes it automatically):
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: supersecretpassword
POSTGRES_DB: myappNever commit Secret YAML files with real values to source control. Use tools like Sealed Secrets, External Secrets Operator, or HashiCorp Vault for production.
Using a Secret: Environment Variables
containers:
- name: api
image: myregistry/myapp:1.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: POSTGRES_PASSWORDOr inject all keys with envFrom:
containers:
- name: api
envFrom:
- secretRef:
name: db-credentialsUsing a Secret: Mounted as Files
containers:
- name: api
volumeMounts:
- name: tls-certs
mountPath: /etc/ssl/certs
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: my-tls-secretConfigMap vs Secret: When to Use Each
| Use Case | ConfigMap | Secret |
|---|---|---|
| Database host/port | ✅ | |
| Feature flags | ✅ | |
| Log level, environment name | ✅ | |
| Nginx/app config files | ✅ | |
| Database passwords | ✅ | |
| API keys, tokens | ✅ | |
| TLS certificates | ✅ | |
| OAuth client secrets | ✅ |
Combining ConfigMap and Secret in a Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-api
spec:
replicas: 3
selector:
matchLabels:
app: my-api
template:
metadata:
labels:
app: my-api
spec:
containers:
- name: api
image: myregistry/myapp:1.0
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: app-config # non-sensitive config
- secretRef:
name: db-credentials # sensitive credentials
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "1000m"Apply all three resources:
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yamlUpdating ConfigMaps and Secrets
Mounted volumes: Kubernetes updates the files in running Pods within about 60 seconds — no Pod restart needed.
Environment variables: Values are set at container start time. To pick up changes, trigger a rolling restart:
kubectl rollout restart deployment/my-apiThis triggers a rolling restart with zero downtime.
Production Best Practices
| Practice | Details |
|---|---|
| Enable Encryption at Rest | Encrypt etcd data so Secrets aren't stored in plaintext |
| Use RBAC | Limit who can read Secrets in each namespace |
| Avoid Secrets in YAML files | Use External Secrets Operator, Sealed Secrets, or Vault |
| Rotate credentials | Update the Secret and trigger a rolling restart |
| Namespace isolation | Keep production Secrets in a separate namespace with restricted access |
Previous: Lesson 8 — Pods, Deployments & Services | Next: Lesson 10 — Deploy a Docker App to Kubernetes
Part of the Docker & Kubernetes Mastery course.
External references:
