ConfigMaps and Secrets

By Vishvesh Patel · DevOps EngineerUpdated September 12, 2026

Separating configuration from images. ConfigMaps vs Secrets, env vars vs volume mounts, which updates live, and what Secrets really protect.

The same container image should run in dev, staging and production. What changes between them is configuration, database hosts, feature flags, credentials, and Kubernetes provides two objects to supply it at runtime: ConfigMaps for ordinary values, Secrets for sensitive ones.

Getting this split right is what makes an image promotable rather than environment-specific.

Why configuration does not belong in the image

An image with DATABASE_URL baked in is a staging image or a production image, never both. Promoting a build then means rebuilding it, which means the artifact you tested is not the artifact you shipped, and rotating a credential means a rebuild and redeploy of everything that uses it.

Externalising configuration is the third factor of the twelve-factor methodology, and it is the difference between promoting a tested artifact and rebuilding a hopefully-identical one.

ConfigMap or Secret?

ConfigMapSecret
ForNon-sensitive configurationPasswords, tokens, certificates, keys
Stored asPlain text in etcdBase64 in the API, optionally encrypted at rest
Shown by kubectl get -o yamlValues in the clearBase64, trivially decoded
Size limit1 MiB1 MiB
Separate RBACYesYes (and it is the control that matters)

The honest summary: a Secret is a ConfigMap with different defaults and its own RBAC surface. Base64 is encoding, not encryption, and anyone who can get secrets in a namespace can read every one of them.

What a Secret genuinely gives you is worth listing, because it is not nothing:

  • A distinct RBAC resource, so you can grant ConfigMap access without granting Secret access.
  • Values kept out of kubectl describe output and most log lines.
  • Optional encryption at rest in etcd, which must be configured, on managed clusters this usually means enabling envelope encryption with a cloud KMS key.
  • tmpfs for mounted Secret volumes, so the value never touches node disk.

What it does not give you is meaningful protection against someone with read access to the namespace. That is what external secret managers are for.

Two ways to consume them, and one important difference

Both objects can be injected as environment variables or mounted as files. The choice is not cosmetic.

As environment variables:

env:
  - name: LOG_LEVEL
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: LOG_LEVEL
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: app-secret
        key: DB_PASSWORD

As mounted files:

volumes:
  - name: config
    configMap:
      name: app-config
containers:
  - name: app
    volumeMounts:
      - name: config
        mountPath: /etc/app

The difference that matters operationally:

Mounted volumes update live. Environment variables never do.

Change a ConfigMap consumed as a volume and the kubelet refreshes the file inside the running container within a minute or so. Change one consumed as an environment variable and the running pod keeps the old value until it is replaced, because environment variables are fixed at process start.

This catches people constantly: they update a ConfigMap, see no change, and conclude Kubernetes is broken. To pick up a changed environment variable you must restart the pods:

kubectl rollout restart deployment/payments-api

There is one exception to the live-update rule. A volume mounted with subPath does not update. It is mounted once and stays frozen. subPath is how you place a single config file into a directory that already has other contents, so this trips up exactly the people trying to be tidy.

Immutable ConfigMaps and Secrets

Setting immutable: true prevents any further change to the data:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v3
immutable: true
data:
  LOG_LEVEL: info

Two benefits. It removes a whole class of accident, nobody edits production config in place, and it measurably reduces API server load, because the kubelet stops watching immutable objects for changes.

The pattern is to version the name (app-config-v3) and update the Deployment to reference the new one, which also makes config changes visible in your rollout history and rollback-able like any other change.

Where real secret management lives

For production, most teams keep the source of truth outside the cluster and sync it in:

  • External Secrets Operator: reads from AWS Secrets Manager, Parameter Store, Vault or Azure Key Vault and creates native Kubernetes Secrets. The most common choice.
  • Secrets Store CSI Driver: mounts values directly from an external store as files, optionally syncing to a Secret.
  • Sealed Secrets: encrypts a Secret so the encrypted form is safe to commit to Git, decrypted only by a controller in the cluster. The usual answer for GitOps.
  • Vault Agent Injector: injects secrets via a sidecar, with support for dynamic, short-lived credentials.

The common thread is that no plaintext secret is ever committed to a repository, and rotation happens in one place rather than across every manifest.

One thing to do regardless: never commit a Secret manifest with real base64 data to Git. Base64 is not obfuscation, and Git never forgets.

Explore further in this section

Sub-topicWhat you will learn
ConfigMapsCreate ConfigMaps and inject config as env vars or files
SecretsStore sensitive values, registry credentials and TLS material

Quick reference

TaskCommand
Create a ConfigMapkubectl create configmap <n> --from-literal=K=V
From a filekubectl create configmap <n> --from-file=app.conf
Create a Secretkubectl create secret generic <n> --from-literal=K=V
Read a Secret valuekubectl get secret <n> -o jsonpath='{.data.K}' | base64 -d
Registry credentialskubectl create secret docker-registry regcred --docker-server=... --docker-username=... --docker-password=...
TLS Secretkubectl create secret tls <n> --cert=tls.crt --key=tls.key
Pick up an env var changekubectl rollout restart deployment/<n>

Key takeaways

  • Externalise configuration so one image runs everywhere: that is what makes a tested artifact promotable.
  • A Secret is not encrypted by default. Base64 is encoding; the real controls are RBAC and etcd encryption at rest.
  • Mounted volumes update live; environment variables do not. Restart the pods after changing an env-var-backed value.
  • subPath mounts never auto-update, which surprises people trying to place a single file neatly.
  • Prefer immutable: true with versioned names: safer, and it lightens the API server.
  • Never commit real Secret manifests to Git. Use Sealed Secrets or the External Secrets Operator.

Check your understanding

Exercise

A config change that did nothing

You update a ConfigMap consumed by pods as environment variables via envFrom. Minutes later the running pods still report the old value. What is happening?

Exercise

What a Secret actually protects

A team moves database passwords from a ConfigMap into a Secret. What security improvement have they actually gained?


Frequently Asked Questions

What is the difference between a ConfigMap and a Secret?

Functionally they are near-identical key-value objects, both capped at 1 MiB and both consumable as environment variables or mounted files. The differences are that Secrets are a separate RBAC resource so access can be granted independently, their values are base64-encoded and omitted from kubectl describe, they can be encrypted at rest in etcd if the cluster is configured for it, and mounted Secret volumes are backed by tmpfs rather than node disk. Use ConfigMaps for ordinary settings and Secrets for anything sensitive, but do not mistake a Secret for encryption.

Why doesn't my pod pick up an updated ConfigMap?

Because it consumes the value as an environment variable. A process gets its environment once at start-up and it cannot be changed afterwards, so the pod keeps the old value until it is replaced. ConfigMaps mounted as volumes do update in place, typically within a minute. To apply an environment variable change, run kubectl rollout restart deployment/<name>, which triggers a normal rolling update with no downtime. Note that volumes mounted with subPath also never update, which is a separate and easily missed cause of the same symptom.

Are Kubernetes Secrets encrypted?

Not by default in the sense people usually mean. Values are base64-encoded in the API, which is trivially reversible. Kubernetes can encrypt Secrets at rest in etcd, but that must be configured explicitly, on managed clusters it generally means enabling envelope encryption with a cloud KMS key. Even then, anyone with get secrets permission in the namespace reads the plaintext through the API. RBAC is the real control, and for high-value credentials an external manager such as Vault or AWS Secrets Manager, synced in via the External Secrets Operator, is the right answer.

How do I store secrets in Git safely?

Never commit a Secret manifest containing real base64 data. It is not obfuscated and Git history is permanent. The two established approaches are Sealed Secrets, which encrypts a Secret with a cluster-held key so the encrypted file is safe to commit and only the in-cluster controller can decrypt it, and the External Secrets Operator, where Git holds only a reference to a secret stored in AWS Secrets Manager, Parameter Store or Vault. Both keep plaintext out of the repository while staying compatible with GitOps.

Is there a size limit on ConfigMaps?

Yes, 1 MiB, imposed by etcd. It is more than enough for configuration and too little for data. If you are approaching it, the content probably belongs somewhere else: a file fetched at start-up from object storage, a volume, or an init container that materialises it. Very large ConfigMaps also put real pressure on the API server, since every kubelet consuming one watches it for changes unless it is marked immutable.

Should I use environment variables or mounted files?

Mounted files when the value may change while the pod runs, when the application expects a config file on disk, or when the value is large or structured. Environment variables when the value is small, static for the pod's lifetime, and the application reads its configuration from the environment. A practical default is environment variables for simple scalars and volumes for anything file-shaped, while remembering that only volumes refresh without a restart.

Continue Learning

Explore Related Topics

Try the Tool

Practise this in the browser

Kubernetes Playground

Practise kubectl against a simulated cluster in your browser.

When it breaks

Related Resources