NovuSpark
All articles

January 2, 2026 · NovuSpark Team

ConfigMaps, Secrets, and Environment Configuration in Kubernetes

This is the third post in our Kubernetes fundamentals series, building on Pods and Deployments and networking.

Baking configuration values directly into a container image means rebuilding the image every time a value needs to change — the same problem we covered for Terraform variables and Ansible variables earlier in this blog. Kubernetes' answer is two related objects: ConfigMaps for ordinary configuration, and Secrets for anything sensitive — though the actual difference between them is smaller than most people assume.

ConfigMaps: externalizing ordinary configuration

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"
  FEATURE_FLAG_NEW_UI: "true"
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: my-app:1.0
          envFrom:
            - configMapRef:
                name: app-config

envFrom with configMapRef injects every key in the ConfigMap as an environment variable inside the container — LOG_LEVEL, MAX_CONNECTIONS, and FEATURE_FLAG_NEW_UI are all available to the application exactly as if they'd been set with a plain environment: block, without rebuilding the image to change any of them.

Secrets: structurally almost identical, meaningfully different in intent

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  DB_PASSWORD: UzNjdXIzLUFjdHVhbC1QYXNzd29yZA==
      containers:
        - name: web
          image: my-app:1.0
          envFrom:
            - configMapRef:
                name: app-config
            - secretRef:
                name: app-secrets

The value under data is base64-encoded, not encrypted. Base64 is an encoding, not a cipher — anyone with kubectl get access to that Secret can trivially recover the original value:

kubectl get secret app-secrets -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
S3cur3-Actual-Password

This is the single most common misunderstanding people carry about Kubernetes Secrets: the name implies protection the object doesn't actually provide by default. What a Secret does give you, that a ConfigMap doesn't, is: Kubernetes avoids logging its value in most standard output, and — critically, but only if you've configured it — encryption at rest in etcd (the cluster's backing datastore), which is not automatically enabled on every Kubernetes installation and needs to be verified, not assumed.

kubectl get secret app-secrets -o yaml

If that command, run by anyone with reasonable cluster access, immediately reveals a recoverable password — as it does by default — the real fix is usually reaching for a proper secrets-management integration rather than treating a raw Kubernetes Secret as sufficient on its own:

  • External Secrets Operator, syncing values from AWS Secrets Manager, HashiCorp Vault, or a similar external store into Kubernetes Secrets automatically, so the actual secret value never has to be manually base64-encoded and committed anywhere.
  • Sealed Secrets, which lets you commit an encrypted version of a Secret to git safely — decryptable only by a controller running inside the specific target cluster.

Either approach addresses the actual gap: encrypting the value before it's stored as a Kubernetes object, rather than trusting the object's own default protections.

Mounting configuration as files instead of environment variables

Not every application reads configuration from environment variables — some expect a config file at a specific path. Both ConfigMaps and Secrets support this via volume mounts:

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

Each key in the ConfigMap becomes a separate file inside /etc/app/config (a file named LOG_LEVEL containing the text info, for example) — the right approach for an application expecting a real config file on disk, rather than environment variables it has to read individually.

A detail worth knowing: updates don't always restart anything

Update a ConfigMap that's mounted as a volume, and the file inside the running Pod does eventually update automatically (subject to a caching delay, typically under a minute) — but the application itself doesn't automatically reload it unless it's specifically written to watch that file for changes. An application reading environment variables from envFrom gets none of this: those values are set once, at container start, and a ConfigMap update has no effect at all on an already-running container using that mechanism — the Pod needs to be recreated (a rolling restart of the Deployment) to pick up the new values.

What to actually remember from this post

  • ConfigMaps and Secrets are structurally almost identical — both inject key-value data as environment variables or mounted files.
  • A Secret's data is base64-encoded, not encrypted — anyone with reasonable kubectl access can trivially decode it, unless encryption-at-rest and access controls are deliberately configured.
  • For genuinely sensitive values, integrate a real secrets manager (External Secrets Operator, Sealed Secrets) rather than relying on a raw Secret object's default behavior.
  • Environment-variable-based config requires a Pod restart to pick up changes — mounted-file config updates eventually, but the application still needs to actually watch for that change to benefit from it.

Next in the series: Kubernetes Storage: Volumes, PVCs, and StorageClasses, where we cover how an application keeps data that needs to outlive any individual Pod.

Ready when you are

Want training built around your team's real work?

Tell us about your team and what you're trying to solve — we'll recommend a program that fits.