[10/24] M is for ConfigMaps and Secrets: Managing Configuration
This is Post #9 in the Kubernetes A-to-Z Series
Reading Order: Previous: Volumes | Next: Ingress
Series Progress: 10/24 complete | Difficulty: Intermediate | Time: 20 min | Part 3/6: Configuration
Welcome to the ninth post in our Kubernetes A-to-Z Series! Now that you understand persistent storage, let’s explore ConfigMaps and Secrets - the Kubernetes primitives for managing application configuration. These resources enable you to decouple configuration from container images, following the twelve-factor app methodology.
Why Separate Configuration from Code?
Hardcoding configuration in container images creates several problems:
Hardcoded Configuration (Bad):
┌─────────────────────────────────────┐
│ Container Image │
│ ┌─────────────────────────────┐ │
│ │ Application Code │ │
│ │ DB_HOST=prod-db.internal │ │ Must rebuild for
│ │ API_KEY=abc123 │ │ each environment
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
Externalized Configuration (Good):
┌─────────────────────────────────────┐
│ Container Image │
│ ┌─────────────────────────────┐ │
│ │ Application Code │ │ Same image,
│ │ (reads from environment) │ │ different config
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ ConfigMap / Secret │
│ DB_HOST, API_KEY, etc. │
└─────────────────────────────────────┘
Benefits of External Configuration
- Environment Portability: Same image across dev/staging/prod
- Security: Secrets managed separately from code
- Flexibility: Change config without rebuilding images
- Auditability: Track configuration changes in Kubernetes
ConfigMaps
What is a ConfigMap?
A ConfigMap stores non-sensitive configuration data as key-value pairs. Applications can consume ConfigMaps as environment variables, command-line arguments, or configuration files.
Creating ConfigMaps
# From literal values
kubectl create configmap app-config \
--from-literal=DB_HOST=postgres.default.svc \
--from-literal=DB_PORT=5432 \
--from-literal=LOG_LEVEL=info
# From file
kubectl create configmap nginx-config --from-file=nginx.conf
# From directory
kubectl create configmap app-configs --from-file=./config/
# From env file
kubectl create configmap app-env --from-env-file=app.env
ConfigMap YAML Definition
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: webapp-config
namespace: production
data:
# Simple key-value pairs
DB_HOST: "postgres.production.svc"
DB_PORT: "5432"
DB_NAME: "webapp"
LOG_LEVEL: "info"
CACHE_TTL: "3600"
# Multi-line configuration file
app.properties: |
server.port=8080
server.host=0.0.0.0
logging.level=INFO
cache.enabled=true
cache.ttl=3600
# JSON configuration
features.json: |
{
"featureA": true,
"featureB": false,
"maxRetries": 3
}
Using ConfigMaps as Environment Variables
# pod-with-configmap-env.yaml
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
containers:
- name: webapp
image: myapp:v1.0
# Individual environment variables
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: webapp-config
key: DB_HOST
- name: DATABASE_PORT
valueFrom:
configMapKeyRef:
name: webapp-config
key: DB_PORT
# All keys as environment variables
envFrom:
- configMapRef:
name: webapp-config
Using ConfigMaps as Volumes
# pod-with-configmap-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
containers:
- name: webapp
image: myapp:v1.0
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
volumes:
- name: config-volume
configMap:
name: webapp-config
# Optional: select specific keys
items:
- key: app.properties
path: application.properties
- key: features.json
path: features.json
ConfigMap with Specific File Permissions
# configmap-with-permissions.yaml
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
containers:
- name: webapp
image: myapp:v1.0
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: webapp-config
defaultMode: 0644
items:
- key: app.properties
path: app.properties
mode: 0400
Secrets
What is a Secret?
A Secret stores sensitive data like passwords, tokens, and keys. Secrets are base64-encoded and can be encrypted at rest in etcd.
Secret Types
| Type | Description |
|---|---|
| Opaque | Generic secret (default) |
| kubernetes.io/service-account-token | Service account token |
| kubernetes.io/dockerconfigjson | Docker registry credentials |
| kubernetes.io/tls | TLS certificate and key |
| kubernetes.io/basic-auth | Basic authentication |
| kubernetes.io/ssh-auth | SSH credentials |
Creating Secrets
# From literal values
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=secretpassword123
# From file
kubectl create secret generic tls-cert \
--from-file=tls.crt=./cert.pem \
--from-file=tls.key=./key.pem
# Docker registry secret
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=user \
--docker-password=password \
[email protected]
# TLS secret
kubectl create secret tls webapp-tls \
--cert=./tls.crt \
--key=./tls.key
Secret YAML Definition
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
# Base64 encoded values
username: YWRtaW4= # echo -n 'admin' | base64
password: c2VjcmV0cGFzczEyMw== # echo -n 'secretpass123' | base64
---
# Using stringData (auto-encoded)
apiVersion: v1
kind: Secret
metadata:
name: api-credentials
namespace: production
type: Opaque
stringData:
# Plain text (will be base64 encoded automatically)
API_KEY: "my-secret-api-key"
API_SECRET: "my-secret-api-secret"
Using Secrets as Environment Variables
# pod-with-secret-env.yaml
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
containers:
- name: webapp
image: myapp:v1.0
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
# All keys as environment variables
envFrom:
- secretRef:
name: api-credentials
Using Secrets as Volumes
# pod-with-secret-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: webapp
spec:
containers:
- name: webapp
image: myapp:v1.0
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: db-credentials
defaultMode: 0400
TLS Secret for Ingress
# tls-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: webapp-tls
namespace: production
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTi... # Base64 encoded certificate
tls.key: LS0tLS1CRUdJTi... # Base64 encoded private key
Docker Registry Secret
# docker-registry-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: regcred
namespace: production
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6InVzZXIiLCJwYXNzd29yZCI6InBhc3N3b3JkIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwiYXV0aCI6ImRYTmxjanB3WVhOemQyOXlaQT09In19fQ==
Complete Application Example
# complete-config-example.yaml
# ConfigMap for non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: webapp-config
namespace: production
data:
DB_HOST: "postgres.production.svc"
DB_PORT: "5432"
DB_NAME: "webapp"
REDIS_HOST: "redis.production.svc"
LOG_LEVEL: "info"
---
# Secret for sensitive data
apiVersion: v1
kind: Secret
metadata:
name: webapp-secrets
namespace: production
type: Opaque
stringData:
DB_PASSWORD: "super-secret-password"
REDIS_PASSWORD: "redis-secret-password"
JWT_SECRET: "jwt-signing-secret"
---
# Deployment using both
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: myapp:v1.0
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: webapp-config
- secretRef:
name: webapp-secrets
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
Environment-Specific Configuration
Using Kustomize Overlays
# base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: webapp-config
data:
LOG_LEVEL: "info"
CACHE_ENABLED: "true"
# overlays/development/configmap-patch.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: webapp-config
data:
LOG_LEVEL: "debug"
DB_HOST: "postgres.development.svc"
# overlays/production/configmap-patch.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: webapp-config
data:
LOG_LEVEL: "warn"
DB_HOST: "postgres.production.svc"
Immutable ConfigMaps and Secrets
For performance and safety:
# immutable-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: webapp-config-v1
data:
DB_HOST: "postgres.production.svc"
LOG_LEVEL: "info"
immutable: true
Automatic Reload on Config Changes
Using Pod Annotations for Rolling Updates
# deployment-with-config-hash.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
template:
metadata:
annotations:
# Change this to trigger rolling update
configmap-hash: "abc123"
spec:
containers:
- name: webapp
image: myapp:v1.0
envFrom:
- configMapRef:
name: webapp-config
Security Best Practices
1. Use RBAC to Restrict Secret Access
# secret-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["webapp-secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: webapp-secret-reader
namespace: production
subjects:
- kind: ServiceAccount
name: webapp-sa
namespace: production
roleRef:
kind: Role
name: secret-reader
apiGroup: rbac.authorization.k8s.io
2. Enable Encryption at Rest
# encryption-config.yaml (on API server)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-secret>
- identity: {}
3. Use External Secret Management
# external-secrets-example.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: webapp-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmanager
kind: SecretStore
target:
name: webapp-secrets
data:
- secretKey: DB_PASSWORD
remoteRef:
key: production/webapp/db-password
Troubleshooting
# Check ConfigMap
kubectl get configmap webapp-config -o yaml
kubectl describe configmap webapp-config
# Check Secret (values are hidden)
kubectl get secret webapp-secrets -o yaml
kubectl describe secret webapp-secrets
# Decode secret value
kubectl get secret webapp-secrets -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
# Check if pod has correct env vars
kubectl exec -it webapp-xxx -- env | grep DB_
# Check mounted config files
kubectl exec -it webapp-xxx -- cat /etc/config/app.properties
# Check events
kubectl get events --field-selector involvedObject.name=webapp-config
Commands Reference
# ConfigMap Management
kubectl create configmap app-config --from-literal=KEY=value
kubectl create configmap app-config --from-file=config.properties
kubectl get configmaps
kubectl describe configmap app-config
kubectl delete configmap app-config
# Secret Management
kubectl create secret generic db-creds --from-literal=password=secret
kubectl create secret tls webapp-tls --cert=tls.crt --key=tls.key
kubectl get secrets
kubectl describe secret db-creds
kubectl delete secret db-creds
# Decode Secret
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d
Key Takeaways
- ConfigMaps store non-sensitive configuration data
- Secrets store sensitive data (base64 encoded, can be encrypted at rest)
- Both can be consumed as environment variables or volume mounts
- Use immutable ConfigMaps/Secrets for performance
- Implement RBAC to restrict secret access
- Consider external secret managers for production
Next Steps
Now that you understand ConfigMaps and Secrets, you’re ready to explore Ingress in the next post. We’ll learn how to manage external access to services, configure routing rules, and implement TLS termination.
Resources for Further Learning
Series Navigation:
- Previous: V is for Volumes: Persistent Storage in Kubernetes
- Next: I is for Ingress: Managing External Access
Complete Series: Kubernetes A-to-Z Series Overview