Kubernetes Kubernetes

Configuration: ConfigMaps and Secrets

Keep settings and credentials out of your images, inject them into Pods, and avoid the common traps.

14 min Intermediate

If a database URL or a log level is baked into your container image, changing it means rebuilding, pushing, and redeploying the image. Kubernetes separates configuration from images with two objects: ConfigMaps for ordinary settings and Secrets for sensitive values. This guide shows how to create both, how to get them into a Pod, and what Secrets do and don’t protect.

Create a ConfigMap

A ConfigMap is a set of key-value pairs. Values can be short strings or entire files:

apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
data:
  LOG_LEVEL: info
  API_URL: http://api.internal:8080
  motd.txt: |
    Welcome to the demo environment

You can also create one from the command line, which is handy for files:

kubectl create configmap web-config \
  --from-literal=LOG_LEVEL=info \
  --from-file=motd.txt
kubectl describe configmap web-config

ConfigMaps are for small, non-sensitive data (the limit is 1 MiB), and they belong to a namespace. A Pod can only use ConfigMaps from its own namespace.

Use it as an environment variable or a file

The same Pod can consume a ConfigMap both ways:

apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
    - name: web
      image: nginx:1.25
      env:
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: web-config
              key: LOG_LEVEL
      volumeMounts:
        - name: config
          mountPath: /usr/share/nginx/html/motd.txt
          subPath: motd.txt
  volumes:
    - name: config
      configMap:
        name: web-config

Two things are happening here. The env entry copies one key into an environment variable. (envFrom can import every key at once, though keys that aren’t valid variable names get skipped.) The volumes and volumeMounts entries mount the ConfigMap as files, one file per key.

If you mount the whole ConfigMap at a directory like this, the directory is replaced by the mounted files:

      volumeMounts:
        - name: config
          mountPath: /etc/web

That is fine for a directory you own, but it hides anything already at that path. Mounting over an existing config directory, such as /etc/nginx, can break the application, and the container may crash-loop with a “file not found” error. That is what subPath is for, as in the first example: it mounts a single key as a single file and leaves the rest of the directory alone. The trade-off is that files mounted with subPath do not receive later updates to the ConfigMap.

Verify what landed in the container:

kubectl exec web -- ls /usr/share/nginx/html
kubectl exec web -- env | grep LOG_LEVEL

Store credentials in a Secret

A Secret looks like a ConfigMap but is meant for passwords, tokens, and keys:

kubectl create secret generic db-credentials \
  --from-literal=username=app \
  --from-literal=password='S3cr3t-value'

In YAML, use stringData to write plain text and let Kubernetes encode it:

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  username: app
  password: S3cr3t-value

If you write the encoded data field yourself, encode with echo -n 'value' | base64. Without -n, the trailing newline becomes part of the password and authentication fails in confusing ways. Consume a Secret the same way as a ConfigMap:

      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password

Avoid committing Secret manifests with real values to Git. Tools such as Sealed Secrets, External Secrets Operator, and SOPS exist so you can keep encrypted or referenced secrets in a repository.

Know what Secrets do and don’t protect

Base64 is an encoding, not encryption. Anyone who can read the Secret through the API can decode it:

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

By default Secrets are also stored unencrypted in etcd. To actually protect them:

  • Restrict who can read Secrets with RBAC, and remember that anyone who can create a Pod in a namespace can mount its Secrets.
  • Enable encryption at rest for etcd, or use a cloud provider’s key management.
  • Consider an external secret manager if your requirements are strict.

Environment variables are convenient but easy to leak, since they show up in kubectl exec ... env, in crash dumps, and sometimes in logs. Mounting a Secret as a file is often a better choice for sensitive values.

Update configuration on purpose

Environment variables are read once, when the container starts, so a changed ConfigMap or Secret doesn’t reach running Pods. Restart them to pick up the change:

kubectl rollout restart deployment/web

Files from a whole-directory mount do refresh eventually, but many applications only read their config at startup anyway. For automated setups, make the Pod template change whenever the configuration does. That is what the config-hash techniques in the Argo CD Helm and Kustomize guide are for.

Popular Guides