Kubernetes
11 min readMay 3, 2026

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).

CO
Coding Protocols Team
Platform Engineering
Kubernetes ConfigMaps and Secrets: Configuration Management Patterns

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:

yaml
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: password

Caveat: 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:

yaml
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: true
yaml
1# 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):

yaml
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:

yaml
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:

yaml
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):

yaml
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-registry

For 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):

yaml
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: true

Secret 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:

yaml
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: true

The 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 secret access (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:

yaml
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-password

See 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.

Related Topics

Kubernetes
ConfigMaps
Secrets
Configuration Management
Security
Platform Engineering
ESO

Read Next