Kubernetes ConfigMaps & Secrets: Managing Configuration the Right Way

August 12, 2026 · 8 min read

You hardcoded a database password in your image. A week later, someone forks the repo and the credential ships to Docker Hub. Or you baked environment-specific URLs into your application code, and now every environment needs a different image. Both problems have the same solution: ConfigMaps and Secrets — Kubernetes-native objects that separate configuration from containers.

This guide covers what each object is for, how to create and inject them, the differences that actually matter in production, and the mistakes that leak credentials or break rolling updates.

1. ConfigMap vs Secret: What's the Difference?

Both store key-value data and both get injected into pods the same way. The differences are operational:

AttributeConfigMapSecret
StoresNon-sensitive config (URLs, feature flags, app settings)Sensitive data (passwords, API keys, TLS certs)
Value encodingPlain textBase64-encoded (encoding, not encryption)
Storageetcd, readable by anyone with API accessetcd — enable encryption at rest for real protection
ConsumptionEnv vars, volumes, CLI argsEnv vars, volumes, image pull credentials
RotationRecreate pods to pick up changesRecreate pods; mount updates only via volume
The rule of thumb: if you wouldn't paste it into a public README, it belongs in a Secret. Everything else goes in a ConfigMap.

2. Creating ConfigMaps

You can create a ConfigMap from literals, files, or a manifest:

# From literals
kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=info \
  --from-literal=MAX_CONNECTIONS=100

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

# From a whole directory
kubectl create configmap app-config --from-file=./configs/

The declarative version is better for GitOps workflows:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"
  app.properties: |
    cache.ttl=300
    retry.max=3

Note that values in data are always strings. Numbers and booleans must be quoted, and multi-line content is handled with the | block scalar.

3. Injecting Config into Pods

There are two main injection paths, and they behave differently on update.

Environment variables

containers:
- name: app
  image: myapp:1.0
  envFrom:
  - configMapRef:
      name: app-config
  env:
  - name: DB_HOST
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: DB_HOST

envFrom pulls in every key as an environment variable — fast to set up, but it silently fails on invalid keys and can collide with existing env vars. configMapKeyRef is explicit and gives you a specific variable. Use envFrom for teams, explicit refs for critical values.

Volume mounts

volumes:
- name: config-volume
  configMap:
    name: app-config
containers:
- name: app
  image: myapp:1.0
  volumeMounts:
  - name: config-volume
    mountPath: /etc/config

Volume mounts are where the behavior difference matters: volume-mounted config updates in place when the ConfigMap changes (kubelet syncs it every ~60 seconds). Environment variables are fixed at pod start — updating the ConfigMap does nothing until you recreate the pod.

This is the classic source of "I changed the ConfigMap but my app still uses the old value" confusion. If you use env vars, you must trigger a rollout: kubectl rollout restart deployment/app.

4. Working with Secrets

Secrets follow the same patterns, with extra care around how values are written:

# From literals — kubectl base64-encodes for you
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password='S3cure!Pass'

# From a file
kubectl create secret generic tls-keys \
  --from-file=tls.crt --from-file=tls.key

In YAML, values must be base64-encoded — but stringData lets you write plain text and Kubernetes encodes it on apply:

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  username: admin
  password: S3cure!Pass

Critical warning: base64 is not encryption. Anyone with get permission on secrets can decode them with one command:

kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d

That's exactly why the RBAC story for secrets matters (see section 6).

Secret types

Beyond the default Opaque, Kubernetes ships special-purpose types:

# Create a TLS secret for your Ingress
kubectl create secret tls myapp-tls --cert=tls.crt --key=tls.key

# Use a private-registry secret in a pod
spec:
  imagePullSecrets:
  - name: registry-credentials

5. Production Best Practices

Now that the mechanics are clear, here's what separates a working cluster from a secure one:

  1. Mark configuration immutable when possible. immutable: true on ConfigMaps and Secrets prevents accidental edits and improves performance (kubelet skips watching unchanged objects). Only do this when you can recreate the object instead of editing it — common in GitOps pipelines.
  2. Never commit secrets to Git. A secret in a manifest file in your repo is a secret in your history, forever. Use Sealed Secrets, External Secrets Operator, or a vault like HashiCorp Vault to store the ciphertext, not the plaintext.
  3. Enable encryption at rest for secrets. By default secrets sit in etcd as base64. Configure --encryption-provider-config with aescbc or kms so the data is actually encrypted on disk.
  4. Restrict secret access with RBAC. Don't grant blanket get on all secrets — a read on db-credentials is a read on the production password. Scope roles to namespaces and specific resource names.
  5. Name secrets and configmaps clearly. app-config and db-credentials beat config1 and secret-final-v2 when an on-call engineer needs to find the right object at 3 AM.

6. Debugging Configuration Issues

When a pod doesn't behave as expected, these commands reveal what was actually injected:

# What does the ConfigMap actually contain?
kubectl get configmap app-config -o yaml

# What does the Secret decode to? (be careful with this one)
kubectl get secret db-credentials -o jsonpath='{.data}' | base64 -d

# What env vars does the running container see?
kubectl exec myapp-7d4f8b9c6c-xk9j2 -- env | grep DB_

# What files were mounted?
kubectl exec myapp-7d4f8b9c6c-xk9j2 -- ls -la /etc/config

# Why won't the pod start? (missing key, bad volume reference)
kubectl describe pod myapp-7d4f8b9c6c-xk9j2

The most common failure — CreateContainerConfigError — means the pod couldn't resolve a configMapKeyRef or secretKeyRef. The describe output names the missing key; check the spelling and confirm the object exists in the same namespace as the pod.

Summary

ConfigMaps and Secrets are the difference between an image that runs anywhere and a container with a password baked in. The pattern to remember:

Start by moving every hardcoded value out of your images into a ConfigMap. Then move the credentials into Secrets. Then lock down RBAC and enable encryption at rest. Your images get smaller, your deployments get portable, and your security posture stops depending on "nobody forks the repo."