Configuration Management in Kubernetes
ConfigMaps and Secrets decouple configuration from container images
This separation lets you change application behavior without rebuilding images, and keeps sensitive data out of your codebase.
ConfigMaps
Store non-sensitive configuration data in key-value pairs. Use for environment variables, configuration files, and command arguments.
Secrets
Hold sensitive information such as passwords, OAuth tokens, SSH keys, and TLS certificates. Base64 encoded, with optional encryption at rest.
ConfigMap vs Secret Comparison
| Feature | ConfigMap | Secret |
|---|---|---|
| Purpose | Non-sensitive configuration | Sensitive data |
| Data Format | Plain text | Base64 encoded |
| Encryption at Rest | Optional | Recommended |
| RBAC Support | Yes | Yes (more restrictive) |
| Size Limit | 1MB | 1MB |
| Update Behavior | Can be updated | Can be updated |
Best Practice
Always use ConfigMaps for non-sensitive data and Secrets for sensitive data. Never store sensitive information like passwords or API keys in ConfigMaps, even if they are "internal" to your cluster.
Working with ConfigMaps
Creating ConfigMaps
# Create a ConfigMap from literal key-value pairs
kubectl create configmap app-config \
--from-literal=database_url=postgres://localhost:5432/mydb \
--from-literal=cache_size=100 \
--from-literal=log_level=debug
# Create from a properties file
kubectl create configmap app-config --from-file=app.properties
# Create from multiple files
kubectl create configmap app-config \
--from-file=config/ \
--from-file=app.properties
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: default
data:
# Simple key-value pairs
database_url: "postgres://localhost:5432/mydb"
cache_size: "100"
log_level: "debug"
# Multi-line file content
app.properties: |
server.port=8080
server.host=0.0.0.0
debug.enabled=true
nginx.conf: |
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend:8080;
}
}
Using ConfigMaps in Pods
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: myapp:latest
env:
# Individual keys
- name: DATABASE_URL
valueFrom:
configMapKeyRef:
name: app-config
key: database_url
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: log_level
# All keys from ConfigMap
envFrom:
- configMapRef:
name: app-config
prefix: APP_ # Optional prefix for env vars
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
# Mount specific keys to specific paths
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: config-volume
configMap:
name: app-config
# Optional: specify permissions
defaultMode: 0644
- name: nginx-config
configMap:
name: app-config
items:
- key: nginx.conf
path: nginx.conf
mode: 0644
ConfigMap Updates
When a ConfigMap is updated:
- Environment variables do NOT update automatically
- Volume-mounted files update within ~1 minute (kubelet sync period)
- SubPath mounts do NOT update automatically
- Pods must be restarted to pick up env var changes
Working with Secrets
Types of Secrets
Opaque
Default secret type for arbitrary user-defined data. Most common type.
TLS
For storing TLS certificates and private keys for HTTPS.
Docker Registry
For authenticating with private Docker registries to pull images.
Service Account
Automatically created for service accounts to authenticate with the API server.
Creating and Managing Secrets
# Create secret with literal values
kubectl create secret generic db-secret \
--from-literal=username=dbuser \
--from-literal=password='S3cur3P@ssw0rd'
# Create from files
kubectl create secret generic ssh-key-secret \
--from-file=ssh-privatekey=/path/to/.ssh/id_rsa \
--from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
# Create TLS secret
kubectl create secret tls tls-secret \
--cert=path/to/tls.crt \
--key=path/to/tls.key
# Create Docker registry secret
kubectl create secret docker-registry regcred \
--docker-server=myregistry.io \
--docker-username=user \
--docker-password=pass \
--docker-email=user@example.com
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
# stringData allows plain text (converted to base64 automatically)
stringData:
username: admin
password: 'S3cur3P@ssw0rd'
config.yaml: |
apiUrl: https://api.example.com
apiKey: abc123xyz789
timeout: 30
apiVersion: v1
kind: Pod
metadata:
name: secret-pod
spec:
containers:
- name: app
image: myapp:latest
env:
# As environment variables
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
volumeMounts:
# As files in a volume
- name: secret-volume
mountPath: "/etc/secrets"
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: mysecret
defaultMode: 0400 # Read-only for owner
# For pulling images from private registry
imagePullSecrets:
- name: regcred
Security Warning
Secrets are only base64 encoded, NOT encrypted by default. Always enable encryption at rest in etcd, use RBAC to limit Secret access, consider external secret management systems, and never commit Secrets to version control.
Usage Patterns and Advanced Configuration
Application Configuration Pattern
A common production pattern combines ConfigMaps for non-sensitive settings with Secrets for credentials, both mounted into a Deployment.
# ConfigMap for app configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
application.yaml: |
server:
port: 8080
host: 0.0.0.0
features:
cache: enabled
rateLimit: 100
logging:
level: info
format: json
---
# Secret for sensitive data
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
database.yaml: |
host: db.example.com
port: 5432
username: appuser
password: 'V3ryS3cur3P@ss'
---
# Deployment using both
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: config
mountPath: /app/config
- name: secrets
mountPath: /app/secrets
volumes:
- name: config
configMap:
name: app-config
- name: secrets
secret:
secretName: app-secrets
defaultMode: 0400
Environment-Specific Configuration
Use multiple ConfigMaps to layer base settings with environment-specific overrides.
# Base ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-base
data:
APP_NAME: "MyApplication"
LOG_FORMAT: "json"
---
# Production environment
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-prod
data:
ENVIRONMENT: "production"
API_URL: "https://api.example.com"
DEBUG: "false"
LOG_LEVEL: "warn"
---
# Pod that combines configurations
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:latest
envFrom:
# Load base config first
- configMapRef:
name: app-config-base
# Then environment-specific (can override base)
- configMapRef:
name: app-config-prod
Dynamic Configuration Reload
# Deployment with config reloader sidecar
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-reloader
spec:
template:
spec:
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: config
mountPath: /etc/config
# Sidecar to watch for config changes
- name: config-reloader
image: jimmidyson/configmap-reload:v0.5.0
args:
- --volume-dir=/etc/config
- --webhook-url=http://localhost:8080/reload
volumeMounts:
- name: config
mountPath: /etc/config
volumes:
- name: config
configMap:
name: app-config
Immutable ConfigMaps and Secrets
# Immutable ConfigMap (Kubernetes 1.21+)
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v1
immutable: true # Cannot be updated once created
data:
config.yaml: |
version: v1.0.0
features:
newFeature: enabled
---
# Immutable Secret
apiVersion: v1
kind: Secret
metadata:
name: app-secrets-v1
immutable: true
type: Opaque
data:
apiKey: YXBpLWtleS12YWx1ZQ==
Configuration Best Practices
- Version your configs: Use versioned names (app-config-v1, app-config-v2)
- Use namespaces: Organize configs by namespace for isolation
- Validate configs: Test configuration changes in staging first
- Keep configs small: Split large configs into multiple ConfigMaps
- Use tools: Consider Helm or Kustomize for config management
Security Best Practices
Encryption at Rest
Enable etcd encryption for Secrets to protect data stored on disk.
RBAC Policies
Limit Secret access with fine-grained RBAC roles and bindings.
External Secrets
Use HashiCorp Vault or AWS Secrets Manager for production workloads.
Rotation
Regularly rotate sensitive credentials to limit exposure windows.
RBAC for Secrets
# Role to read specific secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["app-secrets", "db-secrets"]
verbs: ["get", "list"]
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-secrets
namespace: production
subjects:
- kind: ServiceAccount
name: app-service-account
namespace: production
roleRef:
kind: Role
name: secret-reader
apiGroup: rbac.authorization.k8s.io
Sealed Secrets Pattern
# Install sealed-secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/controller.yaml
# Create a secret
echo -n mypassword | kubectl create secret generic mysecret \
--dry-run=client \
--from-file=password=/dev/stdin \
-o yaml > mysecret.yaml
# Seal the secret (safe to commit to Git)
kubeseal -f mysecret.yaml -w mysealedsecret.yaml
External Secrets Operator
# SecretStore for AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secret-store
spec:
provider:
aws:
service: SecretsManager
region: us-west-2
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
key: access-key
secretAccessKeySecretRef:
name: aws-credentials
key: secret-key
---
# ExternalSecret that syncs from AWS
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
spec:
refreshInterval: 15m
secretStoreRef:
name: aws-secret-store
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: prod/database/password
Security Anti-Patterns to Avoid
- Storing secrets in ConfigMaps instead of Secrets
- Hardcoding secrets in container images
- Using secrets in Pod commands/args (visible in process listings)
- Logging secret values in application output
- Committing secrets to version control
- Using default service account tokens unnecessarily
Practice Problems
Medium Multi-Environment Application
Deploy an application with different configurations for dev and prod environments. Create base configuration shared across environments, environment-specific overrides, and store database credentials securely.
Use multiple ConfigMaps with envFrom to layer configurations. The later ConfigMap in the list can override keys from the earlier one. Use a Secret for database credentials with secretKeyRef.
# base-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-base-config
data:
APP_NAME: "MyApp"
CACHE_TTL: "3600"
MAX_CONNECTIONS: "100"
---
# prod-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-prod-config
data:
ENVIRONMENT: "production"
API_URL: "https://api.example.com"
DEBUG: "false"
LOG_LEVEL: "info"
---
# secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: dbadmin
password: "Sup3rS3cur3!"
connection-string: "postgresql://dbadmin:Sup3rS3cur3!@db:5432/myapp"
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: myapp:latest
envFrom:
- configMapRef:
name: app-base-config
- configMapRef:
name: app-prod-config
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
Medium TLS Certificate Management
Configure HTTPS for your application using TLS certificates stored as Secrets. Generate certificates, create a TLS Secret, configure an Ingress, and mount certs in a pod.
Use kubectl create secret tls with --cert and --key flags. Reference the secret in an Ingress spec.tls block. For pod-level TLS, mount the secret as a volume with restrictive file permissions.
# Generate self-signed certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=myapp.example.com/O=myapp"
# Create TLS secret
kubectl create secret tls myapp-tls \
--cert=tls.crt \
--key=tls.key
# ingress-tls.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
spec:
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
Hard Implement Secret Rotation
Design a system for rotating database credentials without downtime. Create versioned secrets, implement graceful credential rotation, use init containers for validation, and automate the process.
Use versioned secret names (db-secret-v1, db-secret-v2). Update the Deployment to reference the new secret version, which triggers a rolling update. Use an init container to validate the new credentials before the main container starts.
# Create versioned secrets
apiVersion: v1
kind: Secret
metadata:
name: db-credentials-v2
labels:
app: myapp
version: "v2"
type: Opaque
stringData:
username: dbadmin
password: "N3wR0tat3dP@ss!"
---
# Deployment with init container for validation
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
strategy:
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
spec:
initContainers:
- name: validate-creds
image: postgres:alpine
command: ["sh", "-c"]
args:
- "pg_isready -h $DB_HOST -U $DB_USER || exit 1"
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials-v2
key: username
containers:
- name: app
image: myapp:latest
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials-v2
key: password
Quick Reference
Common kubectl Commands
| Command | Description |
|---|---|
kubectl create configmap NAME --from-literal=KEY=VAL |
Create ConfigMap from literal values |
kubectl create configmap NAME --from-file=FILE |
Create ConfigMap from file |
kubectl create secret generic NAME --from-literal=KEY=VAL |
Create Secret from literal values |
kubectl create secret tls NAME --cert=CRT --key=KEY |
Create TLS Secret |
kubectl get configmaps |
List all ConfigMaps |
kubectl describe secret NAME |
View Secret metadata (not data) |
kubectl get secret NAME -o jsonpath='{.data.KEY}' | base64 -d |
Decode a Secret value |
Remember
ConfigMaps and Secrets are namespaced resources. A Pod can only reference ConfigMaps and Secrets in the same namespace. For cross-namespace access, replicate the resources or use external secret management tools.