Kubernetes Config Management Guide
Master Kubernetes configuration management: ConfigMaps, Secrets, External Secrets Operator, sealed secrets, env injection, volume mounts, and config rotation patterns.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Introduction
Kubernetes separates configuration from container images using ConfigMaps and Secrets. ConfigMaps store non-sensitive configuration as key-value pairs. Secrets store sensitive data like passwords and tokens. External Secrets Operator and Sealed Secrets extend these for GitOps workflows. The following walks through ConfigMaps, Secrets, environment injection, volume mounts, External Secrets Operator, Sealed Secrets, and config rotation patterns.
ConfigMaps
Create a ConfigMap
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
# Key-value pairs
LOG_LEVEL: "info"
DATABASE_HOST: "db.internal"
DATABASE_PORT: "5432"
REDIS_URL: "redis://redis:6379"
# Full file content as a key
nginx.conf: |
server {
listen 80;
location / {
proxy_pass http://backend:3000;
}
}
config.json: |
{
"feature_flags": {
"new_ui": true,
"beta_features": false
},
"limits": {
"max_connections": 100
}
}
Create from files
# Create ConfigMap from a single file
kubectl create configmap nginx-config --from-file=nginx.conf
# Create from multiple files
kubectl create configmap app-config \
--from-file=config.json \
--from-file=settings.yaml \
--from-file=nginx.conf
# Create from a directory
kubectl create configmap app-config --from-file=./config/
# Create from literal values
kubectl create configmap app-config \
--from-literal=LOG_LEVEL=info \
--from-literal=DATABASE_HOST=db.internal
Inject ConfigMap as environment variables
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
containers:
- name: app
image: my-app:latest
env:
# Single value from ConfigMap
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DATABASE_HOST
# All ConfigMap keys as env vars
envFrom:
- configMapRef:
name: app-config
optional: false # Pod fails if ConfigMap doesn't exist
Mount ConfigMap as a file
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config
# Optional: specify which keys to include
items:
- key: config.json
path: config.json
- key: nginx.conf
path: nginx.conf
Mount ConfigMap with subPath (single file)
volumes:
- name: config-volume
configMap:
name: app-config
containers:
- name: app
volumeMounts:
- name: config-volume
mountPath: /app/config.json
subPath: config.json # Mounts only this key
readOnly: true
Secrets
Create a Secret
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
data:
# Base64-encoded values
DATABASE_PASSWORD: cGFzc3dvcmQxMjM= # echo -n "password123" | base64
API_KEY: YWJjZGVmZ2hpamtsbW5vcA==
JWT_SECRET: c3VwZXItc2VjcmV0LWtleQ==
stringData:
# Plain text values (auto-encoded by Kubernetes)
DATABASE_URL: "postgresql://user:pass@db:5432/myapp"
STRIPE_KEY: "sk_live_abc123"
Create from CLI
# Create from literal values
kubectl create secret generic app-secrets \
--from-literal=DATABASE_PASSWORD=password123 \
--from-literal=API_KEY=abcdefg
# Create from file
kubectl create secret generic tls-secret \
--from-file=tls.crt=cert.pem \
--from-file=tls.key=key.pem
# Create Docker registry secret
kubectl create secret docker-registry registry-secret \
--docker-server=registry.io \
--docker-username=user \
--docker-password=pass \
--docker-email=email@example.com
Inject Secret as environment variables
containers:
- name: app
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: DATABASE_PASSWORD
- name: API_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: API_KEY
# All Secret keys as env vars
envFrom:
- secretRef:
name: app-secrets
Mount Secret as files
volumes:
- name: secret-volume
secret:
secretName: app-secrets
defaultMode: 0400 # Read-only by owner
containers:
- name: app
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
TLS Secret
apiVersion: v1
kind: Secret
metadata:
name: tls-secret
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>
# Create TLS secret from files
kubectl create secret tls tls-secret \
--cert=cert.pem \
--key=key.pem
External Secrets Operator
Install ESO
# Install via Helm
helm install external-secrets \
external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace
SecretStore (connects to Vault)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: production
spec:
provider:
vault:
server: "https://vault.internal:8200"
path: "kv"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets"
serviceAccountRef:
name: external-secrets
ExternalSecret (syncs from Vault to Kubernetes Secret)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 1h # Sync every hour
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-secrets # Name of the Kubernetes Secret to create
creationPolicy: Owner
data:
- secretKey: DATABASE_PASSWORD
remoteRef:
key: production/database
property: password
- secretKey: API_KEY
remoteRef:
key: production/api
property: key
- secretKey: JWT_SECRET
remoteRef:
key: production/auth
property: jwt_secret
AWS Secrets Manager SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
Sealed Secrets
Install Sealed Secrets controller
helm install sealed-secrets \
sealed-secrets/sealed-secrets \
--namespace kube-system \
--create-namespace
Seal a secret
# Create a regular secret, then seal it
echo -n 'password123' | base64 > secret.yaml
cat > secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
data:
DATABASE_PASSWORD: cGFzc3dvcmQxMjM=
EOF
# Seal it (safe to commit to Git)
kubeseal --format yaml < secret.yaml > sealed-secret.yaml
SealedSecret resource
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: app-secrets
namespace: production
spec:
encryptedData:
DATABASE_PASSWORD: AgB...encrypted-blob...
template:
metadata:
name: app-secrets
namespace: production
type: Opaque
Config Rotation
Restart pods on ConfigMap change
# ConfigMap change doesn't auto-restart pods.
# Use a hash annotation to force restart on config change.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
metadata:
annotations:
# Hash of ConfigMap content — changes when ConfigMap updates
checksum/config: "{{ .Values.configHash }}"
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config
Reloader (automatic restart on config change)
# Install Reloader — auto-restarts pods when ConfigMaps/Secrets change
helm install reloader stakater/reloader \
--namespace reloader \
--create-namespace
# Annotate deployment to trigger restart on ConfigMap/Secret change
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
annotations:
configmap.reloader.stakater.com/reload: "app-config"
secret.reloader.stakater.com/reload: "app-secrets"
Best Practices
-
For a deeper guide, see Mount Configs and Secrets into Kubernetes Pods.
-
Use ConfigMaps for non-sensitive config — environment variables, feature flags, config files
-
Use Secrets for sensitive data — passwords, tokens, API keys, certificates
-
Never store Secrets in Git as plain text — use External Secrets Operator or Sealed Secrets
-
Use
envFromfor bulk injection — cleaner than listing each variable individually -
Use volume mounts for config files — enables hot reload without pod restart (with Reloader)
-
Use External Secrets Operator for production — syncs from Vault, AWS Secrets Manager, GCP Secret Manager
-
Use Sealed Secrets for GitOps — encrypted secrets safe to commit to version control
-
Set
optional: falseon critical ConfigMaps — pod fails fast if config is missing -
Use
defaultMode: 0400on Secret volumes — restrict file permissions -
Use Reloader for automatic restarts — pods pick up config changes without manual intervention
-
Namespace your config — use separate ConfigMaps per application or service
-
Use
immutable: truefor static config — improves performance and prevents accidental changes
Common Mistakes
- Storing secrets in ConfigMaps: ConfigMaps are not encrypted. Use Secrets or External Secrets Operator.
- Committing base64 secrets to Git: base64 is encoding, not encryption. Anyone can decode. Use Sealed Secrets or ESO.
- No config rotation: updating a ConfigMap doesn’t restart pods. Use Reloader or checksum annotations.
- Mounting entire ConfigMap when you need one file: use
subPathto mount a single key as a file. - No namespace scoping: ConfigMaps are namespace-scoped. A ConfigMap in
defaultnamespace is invisible toproduction. - Using
stringDataanddatafor the same key:datatakes precedence. Use one or the other per key.
FAQ
What is the difference between ConfigMap and Secret?
ConfigMaps store non-sensitive configuration as plain key-value pairs. Secrets store sensitive data with base64 encoding and additional access controls (RBAC, etcd encryption at rest). Use ConfigMaps for app config, Secrets for credentials.
Are Kubernetes Secrets encrypted?
By default, Secrets are base64-encoded (not encrypted). Enable encryption at rest in etcd with --encryption-provider-config. For true secret management, use External Secrets Operator with Vault or cloud secret managers.
What is External Secrets Operator?
A Kubernetes operator that syncs secrets from external secret managers (Vault, AWS Secrets Manager, GCP Secret Manager) into Kubernetes Secrets. You define an ExternalSecret resource that references a SecretStore, and ESO creates and updates the Kubernetes Secret automatically.
What are Sealed Secrets?
A controller that encrypts Kubernetes Secrets into SealedSecret resources. The encrypted SealedSecret is safe to store in Git. Only the controller in your cluster can decrypt it. Useful for GitOps workflows where everything lives in version control.
How do I update pod config without restarting?
ConfigMaps mounted as volumes are updated automatically (within 60-90 seconds by the kubelet). ConfigMaps injected as environment variables require a pod restart. Use Reloader to automate restarts when ConfigMaps or Secrets change.
Related Resources
Helm Charts: Structure, Templating, Dependencies, Registry
Master Helm charts for Kubernetes: chart structure, templating, values, dependencies, hooks, libraries, registry management, and production patterns for deployment.
GuideGitHub Actions CI/CD: Workflows, Runners, Secrets
Master GitHub Actions for CI/CD: workflows, reusable workflows, composite actions, secrets management, runners, matrix builds, caching, and deployment patterns.
RecipeMount Configs and Secrets into Kubernetes Pods
How to mount ConfigMaps and Secrets into Kubernetes pods using env vars, volumes, projected volumes, and secret management with external secrets.
GuideComplete Guide to GitOps with ArgoCD
Deploy Kubernetes applications with GitOps using ArgoCD. Covers installation, ApplicationSets, sync strategies, Helm/Kustomize, RBAC, and multi-cluster management.
GuideComplete Guide to Kubernetes Ingress
Configure and troubleshoot Kubernetes Ingress controllers. Covers NGINX Ingress, TLS, path routing, annotations, IngressClass, and common pitfalls.