Skip to content

ConfigMaps and Secrets

Config and code should be separated.

In Kubernetes, ConfigMaps and Secrets are the standard resources for injecting runtime configuration into workloads.

  • ConfigMap: non-sensitive configuration.
  • Secret: sensitive data such as credentials and keys.

ConfigMap Basics

Use ConfigMaps for values such as feature flags, endpoints, and app settings.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: info
  API_BASE_URL: https://api.internal.example

Secret Basics

Use Secrets for credentials, tokens, and certificate material.

Prefer stringData for authoring convenience; Kubernetes will encode into data.

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
stringData:
  DB_USERNAME: app
  DB_PASSWORD: supersecret

Base64 encoding is not encryption. Use encryption at rest for etcd and strict RBAC.

Secret types

The type field hints at the intended use:

Type Purpose
Opaque Arbitrary data (default)
kubernetes.io/tls TLS certificate and key (tls.crt, tls.key)
kubernetes.io/dockerconfigjson Registry pull credential
kubernetes.io/service-account-token Auto-bound service account token (legacy)
kubernetes.io/ssh-auth SSH private key
bootstrap.kubernetes.io/token Bootstrap token for node join

Use the correct type so controllers and admission webhooks can handle them appropriately.

Injection Patterns

1) Environment variables

envFrom:
  - configMapRef:
      name: app-config
  - secretRef:
      name: db-secret

Or individual keys:

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-secret
        key: DB_PASSWORD

2) Mounted files

volumes:
  - name: config-vol
    configMap:
      name: app-config
containers:
  - name: app
    image: ghcr.io/example/app:1.0.0
    volumeMounts:
      - name: config-vol
        mountPath: /etc/app-config
        readOnly: true

Secret mount variant:

volumes:
  - name: secret-vol
    secret:
      secretName: db-secret

Update Behavior

What happens after you edit a ConfigMap depends entirely on how it was consumed:

  • Environment variables: never update. They are captured at container start. The only way to pick up changes is a restart (kubectl rollout restart).
  • Mounted volumes: update eventually. The kubelet refreshes mounted keys on its sync cycle -- typically within a minute or so, not instantly.
  • subPath mounts: never update. Mounting a single key via subPath copies the file once; it is permanently frozen. This exception catches a lot of people.
  • Even when files refresh, the application must re-read them. Most servers read config once at boot.

Because "edit the ConfigMap and hope" is so unpredictable, the most reliable production pattern is to treat config as immutable: create a new ConfigMap (versioned name or content hash), point the Deployment at it, and let the normal rollout deliver the change with full rollback support. Kustomize's configMapGenerator automates exactly this pattern.

Security Practices

  • Do not store secrets in Git.
  • Restrict Secret access with namespace-scoped RBAC.
  • Enable etcd encryption at rest.
  • Consider external secret managers (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) with a sync controller like External Secrets Operator or Secrets Store CSI Driver for high-security environments. These keep the secret value out of Kubernetes entirely and inject it at runtime.
  • Use immutable ConfigMaps and Secrets where frequent mutation is not required. Immutable objects are more efficient (no watch on etcd) and prevent accidental modification.

Operational Tips

kubectl get configmap app-config -o yaml
kubectl get secret db-secret -o yaml
kubectl describe pod <pod-name>

Use kubectl describe pod to verify projected env vars and mounted volumes.

Certification notes

  • CKAD staples: kubectl create configmap app-config --from-literal=KEY=value --from-file=config.txt and kubectl create secret generic db-secret --from-literal=password=x. Practice both imperative forms.
  • Decode a secret quickly: kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d.
  • Remember which injection methods update live (volumes) and which don't (env vars, subPath).