Kubernetes ConfigMaps and Secrets: Configuration Management
Hardcoding configuration into container images creates brittle, environment-specific artifacts that violate the twelve-factor app methodology. Every configuration change requires rebuilding images,...
Key Insights
- ConfigMaps and Secrets decouple configuration from container images, enabling the same image to run across multiple environments without rebuilding
- Native Kubernetes Secrets provide only base64 encoding, not encryption—use encryption at rest, RBAC, and consider external secret managers for production workloads
- Immutable ConfigMaps and Secrets prevent accidental updates and enable safer rollouts by forcing explicit version changes through pod recreation
Introduction to Configuration Management in Kubernetes
Hardcoding configuration into container images creates brittle, environment-specific artifacts that violate the twelve-factor app methodology. Every configuration change requires rebuilding images, pushing to registries, and redeploying—a slow, error-prone process that defeats the agility Kubernetes promises.
Kubernetes provides two native primitives for externalizing configuration: ConfigMaps for non-sensitive data and Secrets for credentials and sensitive information. Both allow you to inject configuration at runtime, maintaining a single container image across development, staging, and production environments.
Here’s the anti-pattern you should avoid:
apiVersion: v1
kind: Pod
metadata:
name: hardcoded-config
spec:
containers:
- name: app
image: myapp:1.0
env:
- name: DATABASE_URL
value: "postgres://prod-db:5432/myapp" # Hardcoded!
- name: API_KEY
value: "sk-1234567890abcdef" # Exposed in version control!
The proper approach separates configuration from code:
apiVersion: v1
kind: Pod
metadata:
name: externalized-config
spec:
containers:
- name: app
image: myapp:1.0
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secrets
This separation enables configuration changes without image rebuilds and keeps sensitive data out of your codebase.
ConfigMaps: Managing Non-Sensitive Configuration
ConfigMaps store configuration data as key-value pairs, suitable for application settings, feature flags, configuration files, and any non-confidential information your applications need.
You can create ConfigMaps imperatively using kubectl:
# From literal values
kubectl create configmap app-config \
--from-literal=log_level=debug \
--from-literal=max_connections=100
# From a properties file
kubectl create configmap app-config \
--from-file=application.properties
# From an environment file
kubectl create configmap app-config \
--from-env-file=config.env
# From an entire directory
kubectl create configmap nginx-config \
--from-file=conf.d/
For production, define ConfigMaps declaratively in YAML:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
log_level: "info"
max_connections: "200"
feature_flags.json: |
{
"enable_new_ui": true,
"enable_beta_features": false
}
application.properties: |
server.port=8080
server.compression.enabled=true
Consume ConfigMaps as environment variables:
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: log_level
- name: MAX_CONNECTIONS
valueFrom:
configMapKeyRef:
name: app-config
key: max_connections
For configuration files, mount ConfigMaps as volumes:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d
readOnly: true
volumes:
- name: config
configMap:
name: nginx-config
This approach allows you to update configuration files without rebuilding container images. However, note that environment variables from ConfigMaps only update when pods restart, while mounted volumes can reflect changes after a propagation delay (typically under a minute).
Secrets: Handling Sensitive Data
Secrets store sensitive information like passwords, OAuth tokens, SSH keys, and TLS certificates. While structurally similar to ConfigMaps, Secrets receive special handling: they’re distributed only to nodes running pods that need them, stored in tmpfs (not written to disk), and can be encrypted at rest.
Kubernetes supports several Secret types:
# Generic/Opaque secret (most common)
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=supersecret
# TLS secret for HTTPS certificates
kubectl create secret tls tls-cert \
--cert=path/to/tls.crt \
--key=path/to/tls.key
# Docker registry credentials
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=user \
--docker-password=pass \
--docker-email=user@example.com
In YAML, Secret values must be base64-encoded:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4= # base64 encoded "admin"
password: c3VwZXJzZWNyZXQ= # base64 encoded "supersecret"
Use Secrets in pod specifications:
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
For file-based secrets like certificates, mount as volumes with restricted permissions:
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: tls-certs
mountPath: /etc/tls
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: tls-cert
defaultMode: 0400 # Read-only for owner
For private container registries, reference docker-registry secrets:
apiVersion: v1
kind: Pod
metadata:
name: private-app
spec:
imagePullSecrets:
- name: regcred
containers:
- name: app
image: registry.example.com/private/myapp:1.0
Best Practices and Patterns
Immutable ConfigMaps and Secrets prevent accidental modifications and enable safer rollouts:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v2
immutable: true
data:
log_level: "info"
Once created, immutable ConfigMaps cannot be modified. Configuration changes require creating a new ConfigMap with a different name and updating pod specifications, forcing explicit version changes.
Version your configurations using naming conventions:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-20240115
labels:
app: myapp
version: "20240115"
data:
log_level: "info"
Use Kustomize for environment-specific overlays:
# base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
log_level: "info"
# overlays/production/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
log_level: "warn"
max_connections: "500"
Apply with: kubectl apply -k overlays/production
Namespace your resources to isolate configurations between teams and environments. Never share Secrets across namespaces—create duplicates if necessary.
Security Considerations and Alternatives
Native Kubernetes Secrets have limitations. Base64 encoding is not encryption—anyone with API access can decode Secret values. Enable encryption at rest in your cluster configuration:
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {}
Restrict Secret access with RBAC:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["app-secrets"] # Specific secret only
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-app-secrets
namespace: production
subjects:
- kind: ServiceAccount
name: app-sa
roleRef:
kind: Role
name: secret-reader
apiGroup: rbac.authorization.k8s.io
For production workloads, consider external secret managers. The External Secrets Operator syncs secrets from external sources:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
auth:
kubernetes:
mountPath: "kubernetes"
role: "myapp"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
target:
name: app-secrets
data:
- secretKey: db-password
remoteRef:
key: database/production
property: password
This approach centralizes secret management, provides audit trails, and enables secret rotation without Kubernetes API access.
Troubleshooting and Common Pitfalls
Pods fail to start when referencing non-existent ConfigMaps or Secrets. Check pod events:
kubectl describe pod myapp-pod
# Look for events like:
# Warning Failed Error: configmap "app-config" not found
ConfigMaps and Secrets have a 1MB size limit. For larger configurations, split into multiple resources or use external storage.
Environment variables from ConfigMaps update only on pod restart. Mounted volumes update automatically but with propagation delays. Verify mounted content:
kubectl exec -it myapp-pod -- cat /etc/config/application.properties
Check logs for configuration-related errors:
kubectl logs myapp-pod | grep -i config
When debugging Secret issues, verify the Secret exists and contains expected keys:
kubectl get secret app-secrets -o jsonpath='{.data}'
kubectl get secret app-secrets -o jsonpath='{.data.password}' | base64 -d
Remember that deleting a ConfigMap or Secret while pods are using it doesn’t immediately affect running containers—they retain their mounted volumes and environment variables until restart. However, new pods will fail to start.
ConfigMaps and Secrets form the foundation of configuration management in Kubernetes. Master these primitives, implement proper security controls, and consider external secret managers for production environments. Your deployment pipeline and operational teams will thank you.