LearnKubernetes
Kubernetes5. Configuration·EXPERT·7 min read

ConfigMaps & Secrets

TL;DR

  • ConfigMaps store non-sensitive configuration; Secrets store sensitive data (both base64-encoded, not encrypted by default)
  • Consume ConfigMaps/Secrets as environment variables (immutable after pod start) or volume mounts (live-updated ~60-90s)
  • Volume mount is preferred for Secrets — less likely to leak into logs or process listings than env vars
  • Secret types: Opaque (default), kubernetes.io/tls, kubernetes.io/dockerconfigjson, kubernetes.io/service-account-token
  • Base64 is encoding, not encryption; Secrets are stored unencrypted in etcd unless Encryption at Rest is explicitly configured
  • RBAC get permission on secrets grants trivial access to plaintext values

Core Concepts

ConfigMaps: Non-Sensitive Configuration

ConfigMaps are Kubernetes objects for storing configuration data as key/value pairs. They decouple configuration from container images, allowing the same image to run across environments with different settings (URLs, feature flags, log levels, etc.).

ConfigMaps are designed for non-sensitive data only; they provide no encryption or access control beyond RBAC.

Secrets: Sensitive Data with Base64 Encoding

Secrets store sensitive data (credentials, API keys, certificates, tokens). Data is stored as base64-encoded values — a critical distinction: base64 is encoding for transport and storage, not encryption. Anyone with get secrets RBAC permission can trivially decode base64 values.

By default, Secrets are stored in etcd completely unencrypted unless Encryption at Rest is explicitly configured via EncryptionConfiguration on the API server.

Secret Types

Opaque (default) — Generic key/value pairs for any sensitive data.

kubernetes.io/tls — TLS certificate and key pair for Ingress, webhooks, and other TLS consumers.

kubernetes.io/dockerconfigjson — Docker/container registry authentication credentials for private image pulls.

kubernetes.io/service-account-token — Long-lived service account bearer tokens (largely deprecated in favor of bound tokens).

Consumption Methods: Environment Variables vs. Volume Mounts

Environment Variables:

  • Set once at container start; never updated even if the ConfigMap/Secret is modified
  • May leak into pod descriptions, process listings, crash dumps, or child process environments
  • Best for small, rarely-changing values

Volume Mounts:

  • Files are live-updated within ~60-90 seconds (kubelet sync period); application must watch for file changes and reload
  • Only visible to processes with filesystem access; less likely to leak
  • Best for large configs, certificates, and values that should update without a restart
  • Required for applications that support hot-reload (nginx, Envoy, etc.)

For Secrets specifically, volume mounts are strongly preferred due to reduced exposure to logs and process environments.

Secret Storage and Security

Critical vulnerability: Secrets are stored as plain base64 in etcd by default. Anyone with:

  • Direct etcd access can read all Secrets in plaintext
  • etcd snapshots can be decoded and read offline
  • get secrets RBAC permission can retrieve and decode any Secret

Mitigation: Enable Encryption at Rest via EncryptionConfiguration on the API server, restricting RBAC access to secrets via least-privilege policies.

Configuration Reference

Creating ConfigMaps

# From literal key/value pairs
kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=debug \
  --from-literal=REGION=us-east-1

# From a file (key = filename, value = file contents)
kubectl create configmap nginx-conf --from-file=nginx.conf

# From an entire directory (one key per file)
kubectl create configmap app-config --from-file=config-dir/

ConfigMap Manifest

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "debug"
  REGION: "us-east-1"
  app.properties: |
    server.port=8080
    server.timeout=30s

Creating Secrets

# Generic secret from literals
kubectl create secret generic db-creds \
  --from-literal=username=admin \
  --from-literal=password='S3cur3P@ss'

# TLS secret from cert/key files
kubectl create secret tls my-tls --cert=tls.crt --key=tls.key

# Docker registry secret
kubectl create secret docker-registry my-registry \
  --docker-server=gcr.io \
  --docker-username=_json_key \
  --docker-password="$(cat key.json)"

Consuming as Environment Variables

containers:
- name: app
  envFrom:
  - configMapRef:
      name: app-config
  - secretRef:
      name: db-creds

Consuming as Volume Mounts

volumes:
- name: config-volume
  configMap:
    name: app-config
- name: secret-volume
  secret:
    secretName: db-creds
containers:
- name: app
  volumeMounts:
  - name: config-volume
    mountPath: /etc/config
  - name: secret-volume
    mountPath: /etc/secrets
    readOnly: true

Comparison Table

Aspect ConfigMap Secret
Intended Use Non-sensitive config Sensitive data (credentials, keys)
Storage Encryption No No (unless Encryption at Rest configured)
RBAC Restriction Often minimal Should be highly restricted
Size Limit 1 MB per ConfigMap 1 MB per Secret
Env Var Updates Never after container start Never after container start
Volume Mount Updates Live (~60-90s) Live (~60-90s)

Command Reference

Inspecting ConfigMaps and Secrets

# View ConfigMap contents
kubectl get configmap app-config -o yaml

# Decode a Secret value
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d

# List all Secrets in a namespace
kubectl get secrets -n my-namespace

# View Secret metadata without decoding
kubectl describe secret db-creds

Editing and Updating

# Edit ConfigMap inline
kubectl edit configmap app-config

# Edit Secret inline (displayed in base64)
kubectl edit secret db-creds

# Force pods to restart and pick up ConfigMap changes (env vars only)
kubectl rollout restart deployment my-app

Checking Encryption Configuration

# Check if Encryption at Rest is enabled
ps aux | grep kube-apiserver | grep encryption-provider-config

# View API server manifest on control-plane node
cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep encryption

Common Pitfalls

Pitfall — ConfigMap/Secret Updates Don’t Propagate to Running Pods Environment variables set at pod start time are never updated, even after ConfigMap/Secret modifications.

  • Why: Environment variables are immutable at the container level; they are set once during container initialization.
  • Fix: For env var consumers, force a rolling restart: kubectl rollout restart deployment. For volume mounts, the kubelet updates files within ~60-90 seconds, but the application must reload the file to see changes.

Pitfall — Volume-Mounted Config Updated But Application Still Uses Old Values ConfigMap files are updated on disk, but the application still reads old values.

  • Why: Most applications cache configuration in memory at startup. File updates don’t automatically trigger application reloads.
  • Fix: Either implement hot-reload logic in the application (using inotify or polling), or restart the pod to force re-reading from disk.

Pitfall — Assuming “Secret” Means “Encrypted” Teams treat Secrets as encrypted at rest, then are shocked to find plaintext credentials in etcd backups.

  • Why: Base64 is encoding, not encryption. Secrets are stored as plain base64 in etcd unless Encryption at Rest is explicitly configured.
  • Fix: Enable Encryption at Rest on the API server. Restrict RBAC get and list on secrets as though they were the most sensitive resource in the cluster.

Pitfall — Env Var Secrets Leak in Logs and Process Listings Application crashes log all environment variables, exposing credentials.

  • Why: Environment variables are visible in ps, printenv, crash dumps, and many logging frameworks.
  • Fix: Always mount Secrets as volumes, never as environment variables. Implement log scrubbing to redact sensitive fields.

Pitfall — Over-Privileged RBAC on Secrets Developers have broad get/list on all secrets instead of scoped to specific Secret names.

  • Why: Reduces blast radius if a developer’s account is compromised.
  • Fix: Use RBAC rules that grant get only on specifically named Secrets, not broad list permissions.

Interview Questions

Q — Explain the difference between ConfigMaps and Secrets. What is the primary security distinction?

Q — Why is base64 not encryption? What is the actual threat model Secrets protect against?

Q — Compare environment variable vs. volume mount consumption methods for Secrets. Which is preferred and why?

Q — A ConfigMap is updated, but running pods still see old values. What consumption method was used, and how do you fix it?

Q — How would you ensure Secrets are actually encrypted at rest in a Kubernetes cluster? What configuration is required?

Q — Design an RBAC policy for Secrets in a multi-tenant cluster where different teams own different namespaces.

Q — What does kubernetes.io/dockerconfigjson contain, and why should it be treated as the highest-sensitivity Secret type?

Q — Walk through the complete process of mounting a TLS Secret into an Ingress or webhook controller.

🔒 Locked
Sign in and unlock the full syllabus to keep reading.
Sign in to continue
Knowledge check
5 scenario questions on this topic
Take the quiz →