Skip to content

ConfigMaps and Secrets

Assumes: Pods and Deployments and Namespaces - both objects here are namespace-scoped.

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 (Role-Based Access Control - the rules deciding who may read which objects).

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.
  • Even when files refresh, the application must re-read them. Most servers read config once at boot.

subPath mounts never update

Mounting a single key with subPath copies the file once at container start; it is permanently frozen, even though the surrounding volume mount would have refreshed. This exception catches a lot of people, because the same ConfigMap mounted normally does update.

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

Key Takeaways

  • ConfigMaps and Secrets are namespace-scoped objects that decouple configuration from images.
  • Secrets are base64-encoded, not encrypted - encryption at rest is a cluster setting you must turn on.
  • How you inject config decides whether updates are ever seen: env vars never update, volume mounts update eventually, subPath mounts never update.
  • The reliable production pattern is immutable config: new versioned ConfigMap, new rollout, full rollback.
  • An application that reads config only at boot needs a restart regardless of what the mount does.

Check Yourself

You edit a ConfigMap consumed as environment variables and the pods keep serving old values, even after ten minutes. Bug or expected?

Expected. Environment variables are materialized once when the container starts, so nothing can change them in place. kubectl rollout restart deployment/<name> replaces the pods and picks up the new values.

Your teammate says Secrets are encrypted so it is fine to commit one to Git. What is wrong with that?

Secret values are base64-encoded, which is encoding, not encryption - anyone with the file can decode them in one command. Encryption applies only at rest in etcd, and only if the cluster has an encryption provider configured. A committed Secret is a committed credential.

You mount a ConfigMap key at /etc/app/config.yaml using subPath, and updates never arrive. Why, and what would you change?

subPath copies the file into the container's filesystem once rather than projecting the live volume, so it is frozen for the container's lifetime. Mount the whole volume at a directory instead, or accept the freeze and adopt the immutable-ConfigMap-plus-rollout pattern.

Why is creating a new ConfigMap with a hashed name usually better than editing the existing one?

Because it makes the config change part of the Deployment's pod template, which means it triggers a normal rollout, is visible in rollout history, and can be rolled back. Editing in place changes behavior with no deployment record and unpredictable propagation.


Beginner track - step 8 of 12. Next: Resource Requests and Limits. Back to the track overview.