Kubernetes ConfigMaps and Secrets: Configuration Management Patterns
ConfigMaps and Secrets are how Kubernetes decouples application configuration from container images. The patterns matter as much as the API: environment variables vs. volume mounts, immutable ConfigMaps for cache efficiency, Secret rotation without pod restarts, and the clear boundary between what belongs in ConfigMaps (non-sensitive config) versus what should never touch a Kubernetes Secret at all (use External Secrets Operator instead).

ConfigMaps and Secrets are simple APIs with a lot of operational surface area. The decision tree: non-sensitive configuration → ConfigMap. Sensitive data (passwords, tokens, keys) → External Secrets Operator pulling from AWS Secrets Manager or Vault, not a native Kubernetes Secret. Native Secrets are base64-encoded and stored in etcd. On self-managed clusters, etcd encryption at rest is off by default (requires explicit EncryptionConfiguration). EKS encrypts etcd with AWS-managed keys by default, but anyone with kubectl get secret RBAC, etcd backup access, or API server audit log access can still read Secret values — encryption at rest doesn't protect against in-cluster access.
When you do use native Secrets (for TLS certs managed by cert-manager, for imagePullSecrets, for projected service account tokens), the patterns below apply.
ConfigMap Patterns
Environment Variables
The simplest pattern — but creates a hard dependency on pod restarts for config changes:
1apiVersion: v1
2kind: ConfigMap
3metadata:
4 name: payments-config
5 namespace: payments
6data:
7 APP_ENV: "production"
8 LOG_LEVEL: "info"
9 DB_HOST: "payments-postgres.payments.svc.cluster.local"
10 DB_PORT: "5432"
11 MAX_CONNECTIONS: "50"
12 CACHE_TTL_SECONDS: "300"
13
14---
15spec:
16 containers:
17 - name: payments-api
18 envFrom:
19 - configMapRef:
20 name: payments-config # Inject all keys as environment variables
21 env:
22 # Override specific keys or add values from Secrets
23 - name: DB_PASSWORD
24 valueFrom:
25 secretKeyRef:
26 name: payments-db-credentials
27 key: passwordCaveat: Environment variables are set at container start. Changing the ConfigMap does not update running pods — you must restart the pod (roll the Deployment). Use this for config that changes rarely.
Volume Mounts: Hot-Reloadable Config
Volume-mounted ConfigMaps update automatically (within the kubelet's syncFrequency, default 1 minute — often faster with the default Watch detection strategy). Applications that watch the file (nginx, Prometheus, Envoy) reload without pod restarts:
1spec:
2 volumes:
3 - name: app-config
4 configMap:
5 name: payments-config
6 # Mount only specific keys as files
7 items:
8 - key: app.yaml
9 path: app.yaml
10
11 containers:
12 - name: payments-api
13 volumeMounts:
14 - name: app-config
15 mountPath: /etc/payments/
16 readOnly: true1# ConfigMap with structured config file content
2apiVersion: v1
3kind: ConfigMap
4metadata:
5 name: payments-config
6 namespace: payments
7data:
8 app.yaml: |
9 server:
10 port: 8080
11 readTimeout: 30s
12 writeTimeout: 60s
13
14 database:
15 host: payments-postgres.payments.svc.cluster.local
16 port: 5432
17 maxConnections: 50
18 idleTimeout: 10m
19
20 cache:
21 ttl: 300s
22 maxSize: 1000
23
24 feature-flags.json: |
25 {
26 "new-payment-flow": true,
27 "beta-dashboard": false
28 }The application reads /etc/payments/app.yaml and /etc/payments/feature-flags.json. When the ConfigMap is updated, kubelet rewrites the files. The application needs to watch for file changes (inotify or polling) to pick them up without restart.
Immutable ConfigMaps
Setting immutable: true prevents updates and improves kubelet performance (kubelet doesn't need to watch the ConfigMap for changes):
1apiVersion: v1
2kind: ConfigMap
3metadata:
4 name: payments-config-v2
5 namespace: payments
6immutable: true # Cannot be updated after creation
7data:
8 DB_HOST: "payments-postgres.payments.svc.cluster.local"
9 VERSION: "2.1.0"For immutable ConfigMaps, rolling updates require creating a new ConfigMap with a new name and updating the Deployment to reference it. This is the safe pattern for configuration changes that require atomicity — you can roll back by pointing to the previous ConfigMap name.
Automated Rolling: The Reloader Pattern
In 2026, for ConfigMaps and Secrets that are NOT immutable, we use Reloader to automate pod restarts. It watches for changes and triggers a rolling update automatically:
1kind: Deployment
2metadata:
3 name: payments-api
4 annotations:
5 reloader.stakater.com/auto: "true" # Watch all referenced ConfigMaps/Secrets
6spec:
7 # ...Reloader ensures that your application always uses the latest configuration without manual intervention or the need for complex in-app file-watching logic. It is the industry standard for bridging the gap between ConfigMap updates and running pods.
Secret Patterns
TLS Secrets (cert-manager managed)
cert-manager creates and rotates TLS Secrets automatically. The pattern: reference in Ingress, let cert-manager manage the Secret lifecycle:
1# cert-manager creates this Secret — do not create or manage it manually
2# Ingress references:
3spec:
4 tls:
5 - hosts: [api.codingprotocols.com]
6 secretName: api-tls # cert-manager populates this
7
8# The Secret contents (managed by cert-manager):
9# type: kubernetes.io/tls
10# data:
11# tls.crt: <base64 PEM cert>
12# tls.key: <base64 PEM key>imagePullSecrets
For private registries (non-ECR — ECR uses IAM role, not stored credentials):
1# Create once in the namespace
2kubectl create secret docker-registry ghcr-registry \
3 --docker-server=ghcr.io \
4 --docker-username=my-org-bot \
5 --docker-password=$GITHUB_PAT \
6 --namespace=payments
7
8# Reference in Pod spec
9spec:
10 imagePullSecrets:
11 - name: ghcr-registryFor ECR, use the ECR credential helper or an automated renewal CronJob — ECR tokens expire every 12 hours and should not be stored as long-lived Secrets.
Projected Tokens
For workloads that need a short-lived Kubernetes service account token (not a Secret):
1spec:
2 volumes:
3 - name: api-token
4 projected:
5 sources:
6 - serviceAccountToken:
7 path: token
8 expirationSeconds: 3600
9 audience: "https://my-api.internal" # Audience-bound
10
11 containers:
12 - name: app
13 volumeMounts:
14 - name: api-token
15 mountPath: /var/run/secrets/myapp
16 readOnly: trueSecret Rotation Without Pod Restart
When a Secret's content changes (e.g., a rotated database password), volume-mounted Secrets update automatically — the same as ConfigMaps. Environment variables from Secrets do not update until the pod restarts.
For secrets that rotate frequently (database passwords on 24h rotation), mount as a file:
1spec:
2 volumes:
3 - name: db-credentials
4 secret:
5 secretName: payments-db-credentials
6 items:
7 - key: password
8 path: db-password
9
10 containers:
11 - name: payments-api
12 volumeMounts:
13 - name: db-credentials
14 mountPath: /var/run/secrets/payments
15 readOnly: trueThe application reads /var/run/secrets/payments/db-password on each database connection acquisition (not cached in memory). When the Secret is updated (by ESO from Secrets Manager), the file updates within the kubelet's sync interval (default 1 minute, often faster) and the next connection uses the new password.
What NOT to Store in Kubernetes Secrets
Kubernetes Secrets are base64-encoded in etcd. On self-managed clusters, etcd encryption at rest is off by default. Even when etcd encryption at rest is enabled, anyone with:
kubectl get secretaccess (RBAC)- etcd backup access
- API server audit log access (values logged in some configurations)
- A compromised node (kubelet can read Secrets for pods on that node)
...can read your secrets.
Don't store in native Kubernetes Secrets:
- Database passwords
- API keys
- OAuth tokens
- SSH private keys
- Encryption keys
Do store externally and sync with ESO:
1# Instead of a native Secret, use ExternalSecret
2# The Secret content lives in AWS Secrets Manager; ESO creates the Kubernetes Secret
3apiVersion: external-secrets.io/v1beta1
4kind: ExternalSecret
5metadata:
6 name: payments-db-credentials
7 namespace: payments
8spec:
9 refreshInterval: 1h # Re-sync from Secrets Manager every hour
10 secretStoreRef:
11 name: aws-secretsmanager
12 kind: ClusterSecretStore
13
14 target:
15 name: payments-db-credentials # Creates this Kubernetes Secret
16 creationPolicy: Owner
17
18 data:
19 - secretKey: password
20 remoteRef:
21 key: /payments/production/db-passwordSee External Secrets Operator for Kubernetes for the full ESO setup.
Frequently Asked Questions
Why does my ConfigMap change not take effect after kubectl apply?
If injected as environment variables (env or envFrom), the pod must restart to pick up changes. Volume-mounted ConfigMaps update within the kubelet's sync interval (default 1 minute, often faster) without restart. The application must also re-read the file — many apps read config once at startup. Check whether your application watches for file changes.
Can I use ConfigMaps for feature flags?
Yes — volume-mounted JSON or YAML feature flags work well. The application polls the file or uses inotify. The limitation: ConfigMaps have a 1 MiB size limit. For complex feature flag systems (targeting by user ID, A/B testing percentages), use a dedicated feature flag service (LaunchDarkly, Unleash, or GrowthBook).
Should Kubernetes Secrets be used for database passwords in production?
Only if you also enable etcd encryption at rest and restrict get secret RBAC to the minimum set of identities. The operationally better approach is External Secrets Operator + AWS Secrets Manager — the password lives in Secrets Manager (encrypted, access-logged, rotation-supported), and ESO creates a Kubernetes Secret that's limited-lifetime and can be wiped and re-created from the source of truth at any time.
For External Secrets Operator that replaces native Kubernetes Secrets for sensitive data, see External Secrets Operator for Kubernetes. For RBAC controls that restrict which service accounts can read Secrets, see Kubernetes RBAC Advanced Patterns.
Managing application configuration across multiple environments on EKS? Talk to us at Coding Protocols — we help platform teams design configuration management patterns that keep secrets out of Git and config changes deployable without pod restarts.


