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.
Explore the Discord architecture interactively
Try it →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 componentsContinue learning
Go deeper on infrastructure and delivery
As an Amazon Associate I earn from qualifying purchases. Codelit may receive a commission at no extra cost to you.
The DevOps Handbook
Gene Kim, Jez Humble · 2021
Case studies from Etsy, Amazon, and Target on rewiring deployment pipelines and org structure.
Site Reliability Engineering
Betsy Beyer, Niall Richard Murphy · 2016
Google's on-call postmortems, error-budget math, and staffing models across 550 pages of practice.
4.7 (1.2k)#1 in Network Disaster & Recovery AdministrationKubernetes: Up and Running
Brendan Burns, Joe Beda · 2022
Builds from a single Pod to multi-cluster deployments with YAML you type, not diagrams you skim.
4.6 (118)#10 in Web ServicesBuilding Microservices
Sam Newman · 2021
600 pages on splitting a monolith: service boundaries, data decomposition, migration order.