Kubernetes ConfigMaps and Secrets: Best Practices
Kubernetes ConfigMaps and Secrets: Best Practices
Complete guide to configuration management in Kubernetes — ConfigMaps for plain data, Secrets for sensitive data, and when to use which.
Table of Contents
ConfigMap vs Secret
| ConfigMap | Secret | |
|---|---|---|
| Data | Plain text, non-sensitive | Base64-encoded, sensitive |
| Size limit | 1 MiB | 1 MiB |
| Encryption | Not encrypted at rest | Can be encrypted at rest (encryption config) |
| Use for | Config files, env vars, CLI args | Passwords, tokens, keys, certs |
| Security | Anyone can read | RBAC can restrict access |
Rule: If it shouldn't be in a README, it goes in a Secret.
Creating ConfigMaps
From Literal Values
kubectl create configmap app-config \
--from-literal=LOG_LEVEL=debug \
--from-literal=MAX_CONNECTIONS=100 \
--from-literal=FEATURE_FLAG_X=true
From a File
kubectl create configmap nginx-config \
--from-file=nginx.conf
From a Directory
kubectl create configmap app-config-dir \
--from-file=./config/
YAML Manifest
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "debug"
MAX_CONNECTIONS: "100"
database.conf: |
host = db.example.com
port = 5432
pool_size = 10
Creating Secrets
Generic Secret
kubectl create secret generic db-creds \
--from-literal=username=admin \
--from-literal=password='S!B*d$zDsb='
Docker Registry Secret
kubectl create secret docker-registry reg-cred \
--docker-server=registry.example.com \
--docker-username=user \
--docker-password=pass
TLS Secret
kubectl create secret tls tls-cert \
--cert=path/to/tls.crt \
--key=path/to/tls.key
YAML Manifest
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
username: YWRtaW4= # base64("admin")
password: UyFCKmQkenk= # base64 — run `echo -n 'pass' | base64`
Using in Pods
As Environment Variables
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
image: myapp
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-secret
key: password
All Keys at Once (envFrom)
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-secret
Every key in the ConfigMap/Secret becomes an env var.
As Mounted Files
spec:
containers:
- name: app
volumeMounts:
- name: config
mountPath: /etc/config
- name: secrets
mountPath: /etc/secrets
readOnly: true
volumes:
- name: config
configMap:
name: app-config
- name: secrets
secret:
secretName: db-secret
defaultMode: 0400
Each key becomes a file: /etc/config/LOG_LEVEL, /etc/secrets/username, etc.
Best Practices
1. Namespace Secrets per Environment
# Don't share secrets across environments
metadata:
name: db-secret
namespace: production # separate from dev/staging
2. Use RBAC to Restrict Access
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-secret"] # only this secret
verbs: ["get"]
3. Enable Encryption at Rest
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets"]
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-key>
- identity: {}
4. Never Commit Secrets
# Don't do this in git
data:
password: UyFCKmQkenk= # committed to repo = leaked
Use external secret stores or sealed secrets instead.
5. Use immutable ConfigMaps for Large Configs
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
immutable: true # prevents accidental changes
data:
nginx.conf: |
...
Common Issues
Secret Shows as "***" in kubectl
# Base64 is not encryption! Decode easily:
kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d
This is expected — Base64 is encoding, not encryption. The protection comes from RBAC + encryption-at-rest.
ConfigMap Changes Not Reflected
ConfigMap updates don't auto-update env vars in running pods. You must restart pods:
kubectl rollout restart deployment/my-app
Mounted volumes DO update automatically (with a delay of ~1-2 minutes).
"Secret not found" on mount
kubectl describe pod <pod>
# Events: MountVolume.SetUp failed for volume "secrets" : secret "db-secret" not found
# → secret doesn't exist in the pod's namespace
External Secret Management
For production, consider external secret stores:
| Tool | Purpose |
|---|---|
| External Secrets Operator | Syncs from AWS SM, Azure KV, Vault, GCP SM |
| Sealed Secrets | Encrypts secrets into git-safe files |
| HashiCorp Vault | Full secret lifecycle management |
# External Secrets Operator example
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-secret
spec:
secretStoreRef:
name: azure-keyvault
kind: SecretStore
target:
name: db-secret
data:
- secretKey: password
remoteRef:
key: database-password
Related Articles
Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Intermediate
Estimated Reading Time: 15 minutes