Kubernetes ConfigMap vs Secret: Real-World Architecture, Security Pitfalls, and Practical Guide
Kubernetes ConfigMap vs Secret: Practical Differences, Security Realities, and Workflows
If you build or ship containers to Kubernetes, you eventually hit the classic configuration problem: you need your app binary decoupled from your environment variables, JSON configs, certificates, and database credentials. You don't bake database connection strings or API tokens into a container image. Instead, you inject them at runtime.
Kubernetes provides two primitive API objects for this job: ConfigMaps and Secrets. On paper, their roles sound straightforward: non-sensitive data goes into ConfigMaps, sensitive data goes into Secrets. But in daily cluster operations, operational questions emerge quickly:
- Are Kubernetes Secrets actually secure out of the box? (No, they are plain Base64-encoded strings by default.)
- What happens to my application when I update a mounted ConfigMap file?
- When should I use environment variables versus volume mounts?
- How should external tools like HashiCorp Vault, AWS Secrets Manager, or Sealed Secrets fit into this workflow?
Let's unpack how both resources work under the hood, compare their real behavioral differences, point out common production traps, and walk through battle-tested YAML configs.
ConfigMap
PlaintextUsed for application configuration files, command-line arguments, feature flags, port definitions, and public environment variables. Stored as plain UTF-8 text or binary data directly in etcd.
Secret
Base64 / tmpfsUsed for passwords, SSH keys, TLS certificates, OAuth tokens, and database credentials. Mounted into memory via tmpfs volumes on nodes and subject to distinct RBAC policies.
What is a Kubernetes ConfigMap?
A ConfigMap is an API object that stores non-confidential data in key-value pairs. Think of it as your application's external .env file or appsettings.json managed natively inside the cluster.
Pods can consume ConfigMaps in three distinct ways:
- Individual Environment Variables: Injecting specific keys from a ConfigMap directly into container environment variables.
- Bulk Environment Variables: Pulling all key-value pairs from a ConfigMap into the container's environment via envFrom.
- Mounted Files / Volumes: Projecting keys as individual files inside a mounted directory inside the container filesystem.
ConfigMap YAML Example
Here is a standard ConfigMap storing individual values alongside an entire NGINX configuration block:
apiVersion: v1
kind: ConfigMap
metadata:
name: api-service-config
namespace: default
data:
APP_ENV: "production"
LOG_LEVEL: "info"
PORT: "8080"
CACHE_ENABLED: "true"
nginx.conf: |
server {
listen 8080;
server_name localhost;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
What is a Kubernetes Secret?
A Secret is designed specifically for sensitive operational data like tokens, passwords, and private keys. Functionally, it looks and behaves almost identically to a ConfigMap: it stores key-value pairs that can be injected as environment variables or mounted files.
However, Secrets differ in three foundational areas:
- Base64 Encoding by Default: Data strings must be encoded in Base64 within the manifest (or defined using the stringData field).
- RAM-backed Mounts (tmpfs): When mounted as volumes, Secrets are written to an in-memory temporary filesystem (tmpfs) on the worker node. They are never written to physical node storage disks.
- Built-in Typing: Kubernetes ships with specialized secret types (such as kubernetes.io/tls, kubernetes.io/dockerconfigjson, and kubernetes.io/service-account-token) that enforce validation on specific keys.
Secret YAML Example (Using stringData vs data)
Writing Base64 values manually with echo -n "secret" | base64 gets tedious and error-prone fast. Kubernetes solves this with stringData, which accepts raw plaintext in your manifest and automatically encodes it before saving to etcd:
apiVersion: v1
kind: Secret
metadata:
name: api-service-secrets
namespace: default
type: Opaque
stringData:
DB_PASSWORD: "SuperSecurePassword987!"
API_KEY: "prod_live_a89bc45ef20311"
data:
# Base64 equivalent for reference:
# DB_PASSWORD: U3VwZXJTZWN1cmVQYXNzd29yZDk4NyE=
Head-to-Head Comparison: ConfigMap vs Secret
| Attribute | ConfigMap | Secret |
|---|---|---|
| Primary Purpose | Non-sensitive configuration, server configs, application settings | Credentials, tokens, TLS certificates, connection strings |
| Data Format | Plain UTF-8 text or binary (binaryData) | Base64 encoded (data) or auto-encoded plaintext (stringData) |
| Storage on Node | Standard node disk storage when mounted as volume | tmpfs (volatile RAM only, never written to physical disk) |
| Size Limit | 1 MiB total per object | 1 MiB total per object (constrained by etcd limits) |
| Types / Schemas | Generic / untyped | Typed (Opaque, kubernetes.io/tls, dockerconfigjson, etc.) |
| Encryption at Rest | Not encrypted at rest in etcd by default | Can be encrypted at rest in etcd via EncryptionConfiguration |
| RBAC Granularity | Granted via standard configmaps resource | Controlled via distinct secrets resource |
How to Consume ConfigMaps and Secrets in Pods
A configuration object does nothing until a Pod references it. Let's look at the three main consumption patterns and the critical tradeoffs that come with each.
Pattern 1: Injecting as Environment Variables
You can cherry-pick specific keys from either object using valueFrom:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-api
spec:
replicas: 2
selector:
matchLabels:
app: payment-api
template:
metadata:
labels:
app: payment-api
spec:
containers:
- name: app
image: myregistry.com/payment-api:v1.4.0
env:
- name: APP_LOG_LEVEL
valueFrom:
configMapKeyRef:
name: api-service-config
key: LOG_LEVEL
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: api-service-secrets
key: DB_PASSWORD
Pattern 2: Bulk Injection via envFrom
If your ConfigMap or Secret contains dozens of keys matching the exact variable names your application expects, you can load everything at once using envFrom:
spec:
containers:
- name: app
image: myregistry.com/payment-api:v1.4.0
envFrom:
- configMapRef:
name: api-service-config
- secretRef:
name: api-service-secrets
Pattern 3: Mounting as Volumes
Mounting is the most flexible and robust approach for larger configuration files, JSON schemas, or TLS certificates. Kubernetes mounts each key in the ConfigMap or Secret as an individual file inside the specified folder path.
spec:
containers:
- name: app
image: myregistry.com/payment-api:v1.4.0
volumeMounts:
- name: config-volume
mountPath: /etc/app/config
readOnly: true
- name: tls-secret-volume
mountPath: /etc/ssl/certs
readOnly: true
volumes:
- name: config-volume
configMap:
name: api-service-config
- name: tls-secret-volume
secret:
secretName: api-tls-cert
The Dynamic Update Problem: What Happens on Changes?
Here is an operational reality that catches many engineering teams off guard: how Kubernetes updates your pods depends entirely on how you injected the data.
Environment Variables Do Not Live-Update
When you inject values into a Pod as environment variables (via valueFrom or envFrom), the Linux container process receives those variables at initialization. If you update the underlying ConfigMap or Secret in the API server, the running Pod's environment variables will not change. The Pod must be restarted or rolled over by changing the Deployment spec.
Mounted Volumes Live-Update Automatically (Eventually)
When you mount a ConfigMap or Secret as a volume, the Kubelet periodically checks for updates and syncs the changes to the mounted volume using symlinks. However:
- The update is not instantaneous. Sync time depends on the Kubelet sync period (typically up to 60–90 seconds).
- Your application code must actively watch the filesystem (e.g., using fsnotify or file watchers) to reload configuration without a process restart.
- If you mounted a file using subPath to avoid overwriting an entire directory, automatic updates will not occur. Files mounted via subPath are disconnected from the Kubelet update loop.
Common Production Mistakes
1. Storing Database Passwords in ConfigMaps
Developers occasionally put credentials into ConfigMaps because "it's just a non-prod environment." The issue is RBAC. Developers and monitoring tools often have broad get and list permissions on ConfigMaps for debugging, while Secret access is strictly gated. A misplaced credential in a ConfigMap bypasses your cluster's security segmentation.
2. Exceeding the 1 MiB Object Limit
Both ConfigMaps and Secrets share a hard 1 MiB limit imposed by the underlying etcd storage engine. Storing large application bundle files, oversized TLS bundles, or SQLite database files inside ConfigMaps will cause the Kubernetes API server to reject the manifest. For large files, use persistent volumes or object storage (S3/GCS).
3. Inadvertently Exposing Secrets in Container Process Dumps
Injecting sensitive credentials as environment variables makes them visible in /proc/1/environ inside the container, across APM telemetry logs, and in crash stack traces. Prefer mounting Secrets as files in tmpfs volumes when maximum isolation is required.
Modern Secret Management in Production
In production enterprise setups, engineers rarely create native Kubernetes Secret YAML files by hand. Instead, they rely on specialized tooling:
- External Secrets Operator (ESO): Synchronizes secrets directly from external providers (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, Google Cloud Secret Manager) into native Kubernetes Secrets.
- Sealed Secrets (Bitnami): An asymmetric encryption approach where you can safely commit encrypted "SealedSecret" manifests to public Git repositories.
- Secrets Store CSI Driver: Mounts secrets directly from external stores into Pod memory as volumes using Container Storage Interface (CSI) drivers without creating intermediate Kubernetes Secret objects in etcd.
Frequently Asked Questions
Summary and Practical Recommendations
To keep your Kubernetes configuration architecture clean, maintainable, and secure:
- Use ConfigMaps for application parameters, environment flags, log levels, and configuration file templates.
- Use Secrets strictly for credentials, API tokens, encryption keys, and TLS certificates.
- Mount complex configuration files as volumes; use environment variables only for simple runtime flags.
- Implement KMS encryption at rest for etcd and enforce strict RBAC on the secrets resource.
- Use tools like External Secrets Operator or Sealed Secrets to avoid storing raw secrets in Git repositories.
Comments
Post a Comment