Learn / Kubernetes survival kit / Config and secrets

Lesson 4 of 5 7 min

Config and secrets

ConfigMaps and Secrets, how to actually inject them into a Pod, and why a Secret alone isn't encryption at rest.

Same shape, different intent

ConfigMap and Secret are structurally almost identical - both are key/value stores you attach to a Pod. The distinction is intent and handling: a ConfigMap holds non-sensitive configuration (a feature flag, a log level, a non-secret URL); a Secret holds sensitive values (API keys, database passwords, TLS certificates). Kubernetes gives Secrets separate handling in a few ways (not shown in plain kubectl get/describe output by default, RBAC can restrict Secret access separately from ConfigMap access) - but neither is inherently “safe” without you also configuring access control correctly.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  FEATURE_X_ENABLED: "true"
---
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  DB_PASSWORD: cGFzc3dvcmQxMjM=   # base64-encoded, NOT encrypted

Base64 is not encryption

This trips people up constantly: a Secret’s data values are base64-encoded, a reversible encoding, not an encryption scheme. Anyone with API access to read that Secret object can decode it in one command (echo cGFzc3dvcmQxMjM= | base64 -d). Real protection comes from two separate things:

  1. RBAC - who is actually allowed to get/list Secrets in that namespace. This is the primary control, and it’s on you to configure it narrowly.
  2. Encryption at rest - whether the cluster’s backing data store (etcd) encrypts Secret data on disk. Many managed Kubernetes offerings support enabling this; it’s not universally on by default, so check your specific cluster’s configuration rather than assuming it.

Treat a Secret as “access-controlled,” not “encrypted,” unless you’ve specifically verified encryption at rest is enabled.

Two ways to inject into a Pod

As environment variables - simplest, good for a handful of small values:

spec:
  containers:
    - name: app
      envFrom:
        - configMapRef:
            name: app-config
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: DB_PASSWORD

As a mounted volume - better for larger config, a real config file format, or anything you want to update without a Pod restart:

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

Updates behave differently depending on how you injected them

This is the detail that causes real confusion: a mounted file updates automatically, on the kubelet’s own sync interval (typically within a minute or so), with no Pod restart needed - your application does need its own logic to notice the file changed and reload, if that matters for the value. An environment variable, by contrast, is only ever read once, at container start - changing the underlying ConfigMap/Secret does nothing to already-running Pods until they’re restarted (a new rollout, a manual Pod deletion causing a Deployment to recreate it). If you need a config change to take effect immediately across running Pods without waiting for a restart, mounted files are the only injection method that supports it.

Key takeaways

  • A ConfigMap holds non-sensitive configuration; a Secret holds sensitive values - they're used almost identically (env vars or mounted files) but Kubernetes treats them differently for access control and display.
  • A Secret's values are base64-ENCODED, not encrypted, by default - base64 is trivially reversible, so a Secret is only as protected as who can read it via the Kubernetes API, unless you separately enable encryption at rest.
  • Injecting config as environment variables is simplest; mounting as files is better for larger config, values that need to reload without a Pod restart, or anything shaped like a real config file.
  • Changing a ConfigMap or Secret does NOT automatically restart Pods using it as an env var - a mounted-file value updates on its own timer, but an env-var value only takes effect on the next Pod restart/rollout.

Quick check

3 questions - see how much stuck.

1. Is a Kubernetes Secret's value encrypted by default?
2. What's a practical reason to mount a ConfigMap as a file instead of injecting it as environment variables?
3. If you update a ConfigMap that's injected as environment variables into a running Pod, what happens to that Pod?