ConfigMaps and Secrets: Managing Application Configuration in Kubernetes
Inject configuration data and sensitive information into Kubernetes pods using ConfigMaps and Secrets. Learn about mounting strategies, environment variables, and security best practices.
ConfigMaps and Secrets: Managing Application Configuration in Kubernetes
Applications need configuration. Database URLs, feature flags, API keys, and connection strings change between environments. Hardcoding these values into container images defeats the purpose of containerization. Kubernetes provides two resources for injecting configuration into pods: ConfigMap and Secret.
This post explains how to create these resources, inject them into pods, and use them securely.
For Kubernetes basics, see the Kubernetes fundamentals post. For advanced patterns like external secret operators, read the Advanced Kubernetes post.
Introduction
ConfigMaps and Secrets feed configuration into your Kubernetes containers. ConfigMaps handle the boring stuff — feature flags, environment-specific settings, config files. Secrets handle the stuff you do not want in plaintext: database passwords, API keys, TLS certs. Both mount into pods as environment variables or files.
The line between them blurs in dev. It sharpens in production. Native Kubernetes Secrets are just base64-encoded in etcd — no encryption by default, and anyone with cluster access can decode them. ConfigMaps are stored in plaintext everywhere. If you are running regulated workloads (PCI, HIPAA, anything with serious compliance requirements), you need to harden both before they touch production.
This post covers the basics: creating and injecting ConfigMaps and Secrets, when to use volume mounts instead of environment variables, and where External Secret Operators fit in. After reading, you will have a config management approach that works across dev, staging, and prod without accidentally leaking anything sensitive.
When to Use / When Not to Use
Use ConfigMaps when
ConfigMaps work well for non-sensitive configuration that changes between environments: feature flags, environment variables, application settings, and configuration files like nginx.conf or config.yaml.
Share ConfigMaps across pods in the same namespace. The key benefit is keeping configuration out of container images so the same image works in dev, staging, and prod.
Use Secrets when
Secrets are for anything that should not appear in plaintext: database passwords, API keys, OAuth tokens, TLS certificates, SSH keys.
Kubernetes stores Secrets as base64-encoded strings in etcd. Without encryption at rest, these are readable by anyone with etcd access. For sensitive production workloads, use External Secret Operators that sync from Vault, AWS Secrets Manager, or similar.
Skip ConfigMaps for
Sensitive data. It is not encrypted by default. If your ConfigMap name ends in _secret or contains passwords, use a Secret instead.
Skip native Kubernetes Secrets for (without encryption at rest)
Highly regulated environments (PCI-DSS, HIPAA) and multi-tenant clusters where you need strict isolation. Enable encryption at rest before using Secrets for anything genuinely sensitive.
ConfigMap/Secret Injection Flow
flowchart TD
CM[ConfigMap<br/>or Secret] -->|kubectl apply| K8s[Kubernetes API Server]
K8s -->|Store| etcd[etcd datastore]
K8s -->|Watch| Pod[Pod with Container]
Pod -->|Mount as| Volume[/etc/config<br/>or /run/secrets]
Pod -->|Inject as| EnvVar[Environment<br/>Variables]
Volume mounts update automatically when the ConfigMap or Secret changes. Environment variables do not update after pod start. For dynamic configuration updates, use volume mounts with application-side file watching or SIGHUP reload.
ConfigMap Creation and Usage Patterns
ConfigMaps store non-sensitive configuration as key-value pairs or files. You create them from literal values, files, or directories.
Creating from literals
kubectl create configmap app-config \
--from-literal=ENVIRONMENT=production \
--from-literal=LOG_LEVEL=info \
--from-literal=MAX_CONNECTIONS=100
Creating from a file
kubectl create configmap nginx-config \
--from-file=nginx.conf
The filename becomes the configmap key. The file contents become the value.
Creating from a directory
kubectl create configmap html-content \
--from-file=html/
Defining in YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
ENVIRONMENT: "production"
LOG_LEVEL: "info"
DATABASE_HOST: "postgres.database.svc.cluster.local"
DATABASE_PORT: "5432"
Injecting ConfigMap into Pods
You can inject ConfigMap values as environment variables or volume mounts.
Environment variables
apiVersion: v1
kind: Pod
metadata:
name: web-app
namespace: production
spec:
containers:
- name: web-app
image: nginx:1.25
envFrom:
- configMapRef:
name: app-config
With envFrom, all keys from the ConfigMap become environment variables. For selective injection, use env:
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
- name: ENVIRONMENT
valueFrom:
configMapKeyRef:
name: app-config
key: ENVIRONMENT
Volume mounts
Mount ConfigMap values as files:
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
containers:
- name: web-app
image: nginx:1.25
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d
readOnly: true
volumes:
- name: config
configMap:
name: nginx-config
items:
- key: nginx.conf
path: default.conf
With this setup, /etc/nginx/conf.d/default.conf contains the contents of the nginx.conf key from the ConfigMap.
Secrets Types and Limitations
Secrets work like ConfigMaps but store sensitive data. Kubernetes stores Secrets as base64-encoded strings, not encrypted. Anyone with API access or etcd access can read Secrets.
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
username: cG9zdGdyZXM=
password: c2VjcmV0cGFzc3dvcmQ=
Generate base64 values:
echo -n "postgres" | base64
echo -n "secretpassword" | base64
Kubernetes Secret types
| Type | Use Case |
|---|---|
| Opaque | Arbitrary key-value pairs |
| kubernetes.io/tls | TLS certificates |
| kubernetes.io/dockerconfigjson | Docker registry credentials |
| kubernetes.io/basic-auth | Basic auth credentials |
TLS Secret
apiVersion: v1
kind: Secret
metadata:
name: tls-cert
namespace: production
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
Mounting vs Environment Variables
Both approaches work, but they have different properties:
| Aspect | Environment Variables | Volume Mounts |
|---|---|---|
| Update propagation | Pod must restart | Files update automatically (with sync wave) |
| Visibility | Visible in kubectl exec output | Less exposed |
| Format | Single values | Files |
| Size limit | Not suitable for large configs | Good for config files |
Environment variables work well for simple string values. Volume mounts are better for configuration files, certificates, and other structured data.
For configuration files that change, volume mounts update automatically. Kubernetes syncs the ConfigMap to the volume within a short delay (configurable via ConfigMap data section). Your application needs to watch for file changes or reload on SIGHUP.
External Secret Operators
Kubernetes Secrets have limitations. They are not encrypted by default, they have strict size limits (1MB), and they do not integrate with external secret managers. External Secret Operators bridge this gap.
Tools like External Secrets Operator (ESO), HashiCorp Vault, and AWS Secrets Manager integrate with Kubernetes to sync secrets from external stores.
External Secrets Operator example
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: db-secrets
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: production/database
property: password
This ExternalSecret syncs the password field from the Vault key production/database into a Secret named db-secrets. The Operator handles rotation and updates.
ESO supports AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault, and other providers.
Security Hardening for Secrets
Since Kubernetes does not encrypt Secrets by default, you need additional measures for sensitive data:
Enable encryption at rest
Configure the kube-apiserver to encrypt Secrets before storing them in etcd:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aesgcm:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {}
Enable this in the kube-apiserver configuration:
--encryption-provider-config=/path/to/encryption-config
Use RBAC to restrict Secret access
Limit who can read Secrets using Role-Based Access Control:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
Enable RBAC authentication
Ensure your kube-apiserver has RBAC enabled:
kubectl get pod kube-apiserver -n kube-system -o jsonpath='{.spec.containers[0].command}' | grep -i rbac
Consider third-party solutions
For production workloads with strict security requirements, use a secrets manager with a Kubernetes operator. Vault, AWS Secrets Manager, and similar tools provide audit logs, secret rotation, and encryption that Kubernetes-native Secrets lack.
Trade-off Analysis
ConfigMap Mounting vs Environment Variables
| Aspect | Environment Variables | Volume Mounts |
|---|---|---|
| Update propagation | Requires pod restart | Automatic update within ~1 minute |
| Maximum size | 1MB total per pod env vars | 1MB per ConfigMap, good for config files |
| Application support | Universal — all apps read env vars | Requires file-watch or SIGHUP handling |
| Type safety | String values only | Can map to JSON, YAML files |
| Visibility | Visible in kubectl exec output | Less exposed in process arguments |
Native Secrets vs External Secret Operators
| Aspect | Native Kubernetes Secrets | External Secret Operators (ESO) |
|---|---|---|
| Encryption | Base64 only, not encrypted by default | Full encryption with external provider |
| Rotation | Manual rotation required | Automatic sync and rotation from external store |
| Audit logs | None built-in | Full audit trail from the secret manager |
| Size limit | 1MB | 1MB but better distributed across providers |
| Integration | None | Vault, AWS SM, Azure KV, GCP SM |
ConfigMap Binary Data vs UTF-8 Data
| Aspect | UTF-8 Data (data) | Binary Data (binaryData) |
|---|---|---|
| Encoding | UTF-8 strings directly | Base64-encoded binary content |
| Use case | Config files, text values | TLS certificates, private keys, images |
| Validation | Kubernetes validates as string | No content validation — you manage encoding |
| Size limit | 1MB | 1MB |
YAML vs kubectl create configmap
| Aspect | YAML (kubectl apply) | kubectl create commands |
|---|---|---|
| Version control | Yes — declarative, reviewable | Imperative — not self-documenting |
| Drift detection | Yes | No |
| Reusability | Yes — with kustomize/helm overlays | Limited — hard to adapt for different envs |
| Speed | Slower to type | Faster for quick local dev |
For production, use YAML with GitOps workflows. For local development, kubectl create commands are acceptable for temporary ConfigMaps.
Production Failure Scenarios
Secret Not Found at Pod Startup
If a Secret referenced in a pod does not exist when the pod starts, the pod fails to start with SecretNotFound error.
Symptoms: Pod stuck in Pending or ContainerCreating state, Secrets not found in events.
Diagnosis:
kubectl describe pod <pod-name> -n <namespace>
kubectl get secret <secret-name> -n <namespace>
Mitigation: Create Secrets before applying Pods that reference them. Use depends_on in Helm or kustomize to enforce ordering.
ConfigMap Update Not Propagating to Pods
When a ConfigMap is updated, volume-mounted files reflect the change, but environment variables do not update without pod restart.
Symptoms: Application uses stale configuration even after ConfigMap update.
Mitigation: Use volume mounts for configuration that changes at runtime. Implement SIGHUP handling in your application to reload on file change, or restart pods automatically using a tool like Reloader.
Base64 Encoding Errors in Secrets
Kubernetes Secrets store values as base64 strings. If the base64 encoding is wrong (missing padding, wrong characters), the decoded value is corrupted.
Symptoms: Application fails to connect to database with authentication failed, decoded values are garbled.
Mitigation: Always use echo -n "value" | base64 (note the -n to avoid trailing newline) and echo "b64value" | base64 -d for decoding.
Anti-Patterns
Storing Secrets in ConfigMaps
Never store actual secrets (passwords, API keys) in ConfigMaps. ConfigMaps are not encrypted by default and are visible in YAML exports and etcd dumps. Always use Secret resources for sensitive data.
Not Using binaryData for Non-UTF8 Content
ConfigMaps store data as UTF-8 strings. For binary files (certificates, keys), use the binaryData field instead of data and base64-encode the content.
Copying Entire ConfigMap into Every Environment
Different environments (dev, staging, prod) need different configurations. Copying the same ConfigMap across environments leads to hardcoded production URLs in dev or debug flags in production.
Use Kustomize overlays or Helm values to inject environment-specific configuration.
Interview Questions
Expected answer points:
- ConfigMaps store non-sensitive configuration as UTF-8 key-value pairs
- Secrets store sensitive data (passwords, tokens, keys) but are base64-encoded, not encrypted by default
- Both are namespaced resources accessible by pod volume mounts or environment variables
- Secrets should be used for anything that must not appear in plaintext in YAML exports or etcd
Expected answer points:
- Use `envFrom` with `configMapRef` to inject all keys as environment variables
- Use `env` with `configMapKeyRef` for selective injection of specific keys
- Environment variables are set at pod startup — they do not update when the ConfigMap changes
- For dynamic updates, use volume mounts instead of environment variables
Expected answer points:
- Kubernetes syncs the updated ConfigMap to the volume within a short delay (configurable)
- The application must watch for file changes or reload on SIGHUP signal
- Environment variables do NOT update without pod restart — volume mounts are required for dynamic updates
- Tools like Reloader can automate pod restarts when ConfigMaps change
Expected answer points:
- Kubernetes stores Secrets as base64-encoded strings in etcd — this is not encryption
- Anyone with etcd access (cluster admin, etcd backup files) can read Secret values
- Encryption at rest must be explicitly enabled via kube-apiserver encryption configuration
- For regulated environments (PCI-DSS, HIPAA), native Secrets without encryption are insufficient
Expected answer points:
- ESO syncs secrets from external secret managers (Vault, AWS SM, Azure KV, GCP SM) into Kubernetes Secrets
- It provides automatic rotation, audit logs, and encryption that native Secrets lack
- You define an ExternalSecret resource that references the external store — ESO reconciles them
- Best practice for production workloads in regulated or multi-tenant environments
Expected answer points:
- `data` stores UTF-8 string values as-is
- `binaryData` stores base64-encoded binary content for non-text files like certificates or images
- If you need to store binary content in `data`, you must base64-encode it yourself
- `binaryData` is an alternative that Kubernetes handles as raw binary without UTF-8 validation
Expected answer points:
- Create a Role (namespace-scoped) with `get` and `list` verbs on `secrets`
- Create a ServiceAccount for the pod
- Create a RoleBinding to associate the ServiceAccount with the Role
- The pod uses that ServiceAccount via `spec.serviceAccountName`
Expected answer points:
- Create an EncryptionConfiguration YAML with AES-GCM or another provider
- Pass the config to kube-apiserver via `--encryption-provider-config` flag
- All Secrets written to etcd after enabling the provider are encrypted
- Existing Secrets are not encrypted until they are rewritten (re-created)
Expected answer points:
- Opaque — arbitrary key-value pairs (default type)
- kubernetes.io/tls — TLS certificates and keys
- kubernetes.io/dockerconfigjson — Docker registry credentials
- kubernetes.io/basic-auth — basic authentication credentials
Expected answer points:
- Dev, staging, and prod have different database URLs, API endpoints, and feature flags
- Copying the same ConfigMap means production URLs end up in dev, or debug flags in production
- Use Kustomize overlays or Helm values to inject environment-specific values
- GitOps workflows should treat ConfigMaps as code with environment-specific variants
Expected answer points:
- Verify the ConfigMap exists in the same namespace as the pod: `kubectl get configmap -n
` - Check the pod spec references the correct ConfigMap name and injection method (env vs volume)
- Use `kubectl describe pod
` to see events — look for ConfigMap or Secret not found errors - Verify key names match exactly — Kubernetes is case-sensitive for ConfigMap keys
Expected answer points:
- Both ConfigMaps and Secrets have a maximum size of 1MB
- For very large configuration files, consider storing them in object storage and pulling them at runtime
- Large ConfigMaps impact etcd performance and kubectl response times
Expected answer points:
- Secrets mounted as volumes appear as files in the mount directory (default: `/run/secrets`)
- Each Secret key becomes a file containing the decoded value
- The mount is `tmpfs` — secrets are never written to disk
- Files update automatically when the Secret changes (similar to ConfigMap volume mounts)
Expected answer points:
- Native Kubernetes Secrets do not support automatic rotation — you must manually update and restart pods
- External Secret Operators can automate rotation by monitoring the external secret store
- Application must support SIGHUP reload or be restarted when secrets change
- Use versioned secrets or secret snapshots to avoid breaking running applications during rotation
Expected answer points:
- No — ConfigMaps are namespace-scoped resources
- To share a ConfigMap across namespaces, you must create identical ConfigMaps in each namespace
- Operators can replicate ConfigMaps across namespaces if needed
- Consider an external config store if you need true cross-namespace shared configuration
Expected answer points:
- subPath allows mounting a single key from a ConfigMap as a specific file without exposing all keys
- It is useful for mounting config files that conflict with the directory structure of the container
- The major limitation is that when using subPath, the application does not receive automatic updates when the ConfigMap changes — the pod must be restarted
- subPath bypasses symlink updates that work with regular volume mounts, making it unsuitable for frequently-updated configuration
Expected answer points:
- Set `immutable: true` in the ConfigMap spec to prevent any changes after creation
- Immutable ConfigMaps have significant performance benefits — Kubernetes skips synchronization for ConfigMaps that cannot change
- Use for configuration that must never change at runtime (security policies, feature flags, environment defaults)
- If you need to change an immutable ConfigMap, you must delete and recreate it — there is no update operation
Expected answer points:
- 1MB is the total size limit for both ConfigMaps and Secrets — large configurations must be split across multiple resources
- For application configs exceeding 1MB, consider storing them in object storage (S3, GCS) and pulling at startup or via init container
- For TLS certificates or binary assets, use `binaryData` field with base64 encoding, but be aware of base64 overhead (~33%)
- Large ConfigMaps impact etcd performance and kube-apiserver response times — keep ConfigMaps small
Expected answer points:
- Multiple `envFrom` entries with different ConfigMaps merge all keys into environment variables — if keys conflict, one overwrites the other (order matters)
- Multiple volume mounts from different ConfigMaps create separate files in different directories — no merge, no conflict
- Volume mounts allow files to be updated dynamically; env vars do not update after pod start
- For config that needs to be updated at runtime, use volumes; for static initialization values, use env vars
Expected answer points:
- You can mount both ConfigMaps and Secrets as volumes in the same container, giving the application a unified `/etc/config` or `/run/secrets` directory structure
- Use projected volumes to combine multiple ConfigMaps and Secrets into a single mount point with specific file permissions
- Projected volumes allow you to map specific keys from each resource to specific file names, controlling exactly what the application sees
- This approach separates non-sensitive configuration (ConfigMap) from sensitive data (Secret) while presenting them in a unified file-based interface
Further Reading
- Kubernetes Official Documentation - ConfigMaps
- Kubernetes Official Documentation - Secrets
- External Secrets Operator Documentation
- Encrypting Secrets at Rest in etcd
- RBAC for Kubernetes
Conclusion
Use this checklist when managing ConfigMaps and Secrets:
- Used ConfigMaps for non-sensitive configuration, Secrets for credentials and keys
- Mounted ConfigMaps as volumes for configuration files and dynamic updates
- Injected ConfigMap values as environment variables only for static configuration
- Enabled encryption at rest for etcd to protect Secret data
- Used RBAC to restrict who can read Secrets
- Used External Secret Operators for production workloads requiring audit trails
- Tested application behavior when ConfigMap or Secret values change at runtime
- Used
binaryDatafield for non-UTF8 secret content - Never committed Secret values to version control
- Used
depends_onor ordering tools to ensure Secrets exist before Pods start
Category
Related Posts
Kustomize: Native Kubernetes Configuration Management
Use Kustomize for declarative Kubernetes configuration management without Helm's templating—overlays, patches, and environment-specific customization.
Container Security: Image Scanning and Vulnerability Management
Implement comprehensive container security: from scanning images for vulnerabilities to runtime security monitoring and secrets protection.
Deployment Strategies: Rolling, Blue-Green, and Canary Releases
Compare and implement deployment strategies—rolling updates, blue-green deployments, and canary releases—to reduce risk and enable safe production releases.