Kubernetes ConfigMaps & Secrets — Configuration Management Done Right
Why configuration does not belong in your container image#
Hardcoding database URLs, API keys, and feature flags into your Docker image means rebuilding and redeploying every time a value changes. It also means your production secrets are baked into an artifact that lives in a registry anyone on the team can pull.
Kubernetes solves this with two primitives: ConfigMaps for non-sensitive configuration and Secrets for sensitive data.
ConfigMaps#
A ConfigMap stores key-value pairs of non-sensitive configuration data.
Creating a ConfigMap#
From a YAML manifest:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
DATABASE_HOST: "postgres.internal"
DATABASE_PORT: "5432"
LOG_LEVEL: "info"
FEATURE_NEW_DASHBOARD: "true"
From a file:
kubectl create configmap nginx-conf --from-file=nginx.conf
From literal values:
kubectl create configmap app-config \
--from-literal=DATABASE_HOST=postgres.internal \
--from-literal=LOG_LEVEL=info
What ConfigMaps are for#
- Database hostnames and ports
- Feature flags
- Log levels
- Configuration files (nginx.conf, redis.conf, application.yaml)
- Environment-specific settings (staging vs production URLs)
What ConfigMaps are NOT for#
- Passwords, tokens, API keys — use Secrets
- Large binary blobs — ConfigMaps have a 1 MiB size limit
- Configuration that changes every few seconds — use a feature flag service instead
Secrets#
A Secret stores sensitive data — passwords, tokens, TLS certificates, SSH keys.
Creating a Secret#
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
DB_USERNAME: cG9zdGdyZXM=
DB_PASSWORD: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
The values are base64-encoded, not encrypted. Anyone who can kubectl get secret can decode them. Base64 is encoding, not security.
Using stringData avoids manual base64 encoding:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
DB_USERNAME: postgres
DB_PASSWORD: super-secret-password
Secret types#
| Type | Use case |
|---|---|
Opaque | Generic key-value pairs (default) |
kubernetes.io/tls | TLS certificates (tls.crt + tls.key) |
kubernetes.io/dockerconfigjson | Container registry credentials |
kubernetes.io/basic-auth | Username + password |
kubernetes.io/ssh-auth | SSH private keys |
kubernetes.io/service-account-token | Service account tokens |
Mounting: volumes vs environment variables#
You have two ways to inject ConfigMaps and Secrets into pods.
Environment variables#
spec:
containers:
- name: app
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DATABASE_HOST
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASSWORD
Or inject all keys at once with envFrom:
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-credentials
Pros: Simple. Every language can read env vars. No file system setup needed.
Cons: Environment variables are visible in kubectl describe pod, process listings, and crash dumps. They do not update without restarting the pod. They cannot represent complex file structures.
Volume mounts#
spec:
containers:
- name: app
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config
- name: secret-volume
secret:
secretName: db-credentials
Each key becomes a file in the mounted directory. DATABASE_HOST becomes /etc/config/DATABASE_HOST with the value as file contents.
Pros: Files update automatically when the ConfigMap or Secret changes (within the kubelet sync period, typically 60 seconds). Better for multi-line config files. File permissions can be set.
Cons: Your application needs to watch for file changes or be signaled to reload. More complex pod spec.
Which to choose#
| Requirement | Use |
|---|---|
| Simple key-value config | Environment variables |
| Config files (nginx.conf, app.yaml) | Volume mount |
| Need hot-reload without restart | Volume mount |
| Sensitive values you want to limit exposure | Volume mount (with file permissions) |
| Quick prototyping | Environment variables |
Immutable ConfigMaps and Secrets#
Since Kubernetes 1.21, you can mark a ConfigMap or Secret as immutable:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v3
immutable: true
data:
DATABASE_HOST: "postgres.internal"
LOG_LEVEL: "warn"
Why immutable#
- Performance — the kubelet stops watching immutable ConfigMaps and Secrets, reducing API server load. At scale (thousands of pods), this matters significantly.
- Safety — prevents accidental changes to production configuration. To update, create a new ConfigMap with a new name and update the pod spec.
- Versioning — name your ConfigMaps with a version suffix (
app-config-v3) for easy rollback.
The trade-off#
Updating an immutable ConfigMap requires creating a new one and updating the deployment, which triggers a rolling restart. This is actually a good thing — it makes configuration changes go through the same deployment pipeline as code changes.
Binary data in ConfigMaps#
ConfigMaps support binary data through the binaryData field:
apiVersion: v1
kind: ConfigMap
metadata:
name: binary-config
binaryData:
keystore.jks: |-
BASE64_ENCODED_BINARY_DATA_HERE
data:
app.properties: |
server.port=8080
server.ssl.key-store=/etc/config/keystore.jks
Use cases include Java keystores, compiled protocol buffer definitions, and small binary configuration files. Remember the 1 MiB total size limit per ConfigMap.
Secret encryption at rest#
By default, Kubernetes stores Secrets in etcd unencrypted. Anyone with access to the etcd data directory can read every Secret in your cluster.
Enabling encryption at rest#
Configure the API server with an EncryptionConfiguration:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: BASE64_ENCODED_32_BYTE_KEY
- identity: {}
Encryption providers#
| Provider | Strength | Key management |
|---|---|---|
identity | None (plaintext) | N/A |
aescbc | AES-CBC | Manual key in config file |
aesgcm | AES-GCM (faster) | Manual key in config file |
kms v2 | Envelope encryption | External KMS (AWS KMS, GCP KMS, Vault) |
Always use KMS v2 in production. It uses envelope encryption: Kubernetes generates a data encryption key (DEK) for each Secret, and your external KMS encrypts the DEK. The plaintext DEK never hits disk.
Managed Kubernetes#
- EKS: Supports envelope encryption with AWS KMS. Enable it at cluster creation.
- GKE: Encrypts etcd at rest by default. Optional application-layer encryption with Cloud KMS.
- AKS: Supports envelope encryption with Azure Key Vault.
External secret management#
Kubernetes Secrets have limitations: they are namespace-scoped, stored in etcd, and require RBAC to protect. For production, many teams use external secret stores with Kubernetes operators to sync secrets in.
External Secrets Operator (ESO)#
ESO syncs secrets from external providers into Kubernetes Secrets:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: DB_PASSWORD
remoteRef:
key: production/database
property: password
Supported backends: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, 1Password, Doppler.
Sealed Secrets (Bitnami)#
Encrypt secrets client-side so you can safely commit them to Git:
- Install the
kubesealCLI and the Sealed Secrets controller - Encrypt:
kubeseal --format yaml < secret.yaml > sealed-secret.yaml - Commit
sealed-secret.yamlto Git (safe — it is encrypted with the cluster's public key) - The controller decrypts it in-cluster and creates a regular Secret
HashiCorp Vault with the Vault Agent Injector#
Vault provides dynamic secrets, automatic rotation, and fine-grained access policies. The Vault Agent Injector mutates pods to inject secrets from Vault via init containers and sidecars.
Practical recommendations#
- Never hardcode secrets in images or manifests committed to Git
- Use ConfigMaps for non-sensitive config, Secrets for sensitive data
- Prefer volume mounts for config files and when you need hot-reload
- Use immutable ConfigMaps in production for safety and performance
- Enable encryption at rest — use KMS v2 with your cloud provider's key management
- Use External Secrets Operator to sync secrets from a central secret store
- Restrict Secret access with RBAC — most namespaces should not be able to read Secrets from other namespaces
- Rotate secrets regularly — automate rotation with Vault or your cloud provider's secret manager
Visualize your Kubernetes configuration architecture#
Map out how ConfigMaps, Secrets, and external secret stores connect to your pods — try Codelit to generate an interactive diagram.
Key takeaways#
- ConfigMaps hold non-sensitive config, Secrets hold sensitive data (but are base64-encoded, not encrypted by default)
- Environment variables are simple but cannot hot-reload; volume mounts update automatically
- Immutable ConfigMaps improve performance at scale and prevent accidental changes
- Encryption at rest with KMS v2 is essential — etcd stores Secrets in plaintext by default
- External Secrets Operator syncs from AWS/GCP/Azure/Vault into native Kubernetes Secrets
- Sealed Secrets let you commit encrypted secrets to Git safely
- RBAC on Secrets is critical — without it, any pod in the namespace can read any Secret
This is article #427 of the Codelit engineering blog.
Try it on Codelit
Chaos Mode
Simulate node failures and watch cascading impact across your architecture
Cost Estimator
See estimated AWS monthly costs for every component in your architecture
GitHub Integration
Paste a repo URL and generate architecture from your actual codebase
Related articles
Batch API Endpoints — Patterns for Bulk Operations, Partial Success, and Idempotency
8 min read
system designCircuit Breaker Implementation — State Machine, Failure Counting, Fallbacks, and Resilience4j
7 min read
testingAPI Contract Testing with Pact — Consumer-Driven Contracts for Microservices
8 min read
Try these templates
Headless CMS Platform
Headless content management with structured content, media pipeline, API-first delivery, and editorial workflows.
8 componentsKubernetes Container Orchestration
K8s cluster with pod scheduling, service mesh, auto-scaling, and CI/CD deployment pipeline.
9 componentsProject Management Platform
Jira/Linear-like tool with issues, sprints, boards, workflows, and real-time collaboration.
8 componentsBuild this architecture
Generate an interactive architecture for Kubernetes ConfigMaps & Secrets in seconds.
Try it in Codelit →
Comments