Skip to content
C Codeloom
Kubernetes

Kubernetes ConfigMaps and Secrets Tutorial

A practical walkthrough of ConfigMaps and Secrets in Kubernetes, including how to inject them as environment variables, mount as files, and rotate safely.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • When to use a ConfigMap vs a Secret
  • Injection as env vars vs volume mounts
  • How rotation actually works in running pods
  • Encryption at rest and external secret stores
  • Common security and reload pitfalls

Prerequisites

  • Working knowledge of Pods and Deployments

What and Why

Twelve-factor apps separate code from configuration. In Kubernetes, that separation lives in two objects: ConfigMap for non-sensitive data and Secret for sensitive data. Both look similar on the surface — key/value pairs stored in etcd, injected into pods — but they have different defaults around encoding, access control, and storage encryption.

Getting this right keeps secrets out of your container images and makes config changes possible without rebuilding.

Mental Model

Think of ConfigMaps and Secrets as small in-cluster key/value stores. Pods reference them in two ways:

  • As environment variables: values are baked into the pod at start time. Updates require a pod restart.
  • As volume mounts: values appear as files inside the container. The kubelet refreshes them periodically, so updates can propagate live.
  [ConfigMap]            [Secret]
     |                     |
     +----------+----------+
                |
                v
            [Pod spec]
            /        \
     envFrom         volumeMounts
       |                |
 [env vars set         [/etc/config/*]
  at startup]          (refreshed by kubelet)
Config and secret flow into a pod

Hands-on Example

Create a ConfigMap from literals or files:

kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=info \
  --from-literal=FEATURE_FLAG_X=true

kubectl create configmap nginx-config \
  --from-file=nginx.conf=./nginx.conf

Create a Secret similarly:

kubectl create secret generic db-creds \
  --from-literal=username=appuser \
  --from-literal=password='s3cr3t!'

Inject both into a Deployment:

apiVersion: apps/v1
kind: Deployment
metadata: { name: api }
spec:
  replicas: 2
  selector: { matchLabels: { app: api } }
  template:
    metadata: { labels: { app: api } }
    spec:
      containers:
        - name: api
          image: myorg/api:1.4
          envFrom:
            - configMapRef: { name: app-config }
            - secretRef: { name: db-creds }
          volumeMounts:
            - name: nginx-conf
              mountPath: /etc/nginx/conf.d
              readOnly: true
      volumes:
        - name: nginx-conf
          configMap:
            name: nginx-config

envFrom exposes every key as an environment variable. The volume mount makes nginx.conf appear as a file.

Common Pitfalls

Secrets are base64, not encrypted. By default, etcd stores secrets base64-encoded. Anyone with cluster access can decode them. Enable encryption at rest via an EncryptionConfiguration on the API server, and use cloud KMS as the provider.

Env-var secrets leak into logs. A misbehaving framework that dumps process.env on startup will print your DB password. Prefer file mounts for high-sensitivity values.

No automatic restart on change. Updating a ConfigMap does not restart pods that reference it via envFrom. Either roll the Deployment manually or use a controller like Reloader, or compute a hash of the config and put it in a pod annotation so changes force a rollout:

spec:
  template:
    metadata:
      annotations:
        checksum/config: "{{ .Values.configChecksum }}"

Size limits. ConfigMaps and Secrets are capped at roughly 1 MiB. Large blobs belong in object storage with a pre-signed URL passed via config, not in etcd.

Cross-namespace references are forbidden. Each pod can only reference ConfigMaps and Secrets in its own namespace. Use a tool like External Secrets Operator if you need a single source of truth across namespaces.

Practical Tips

For production, use an external secret store and sync into Kubernetes:

  • AWS Secrets Manager + External Secrets Operator
  • HashiCorp Vault Agent injector
  • GCP Secret Manager CSI driver

These keep the source of truth out of etcd, give you audit logs, and rotate credentials automatically.

To declare secrets in Git without committing plaintext, use Sealed Secrets or SOPS-encrypted manifests. Both encrypt secrets with a cluster-side key that only the controller can decrypt.

When a config change should trigger a rolling restart, run:

kubectl rollout restart deployment/api

This bumps the pod template revision and triggers a normal rollout.

For mounted ConfigMaps, the kubelet sync period is about a minute. Apps that watch their config file (NGINX with inotify, for example) can reload without a restart. Apps that read once at startup cannot.

Always set RBAC carefully on Secrets. A default ServiceAccount with get secrets permission is effectively a credential dump on demand.

Wrap-up

Use ConfigMaps for plain configuration and Secrets for credentials, but treat the latter with real care: base64 is not encryption, env vars leak, and updates need a deliberate rollout. Mount secrets as files when possible, hash your config into pod annotations to trigger rollouts on change, and graduate to an external secret store before your team grows past a handful of services. Treat config as deployment metadata, version it like code, and you will avoid the most common cluster outages.