Skip to content
Innovialabs

Kubernetes ConfigMaps and Secrets: How to Manage App Config

Practice4 min

In short

Keep non-sensitive settings in ConfigMaps and credentials in Secrets, inject them as env vars or mounted files, and remember that base64 is encoding, not encryption — protect Secrets with RBAC, encryption at rest and tools like Sealed Secrets or External Secrets Operator.

The short answer

Kubernetes separates configuration from the container image with two objects:

  • ConfigMap — non-sensitive settings: feature flags, URLs of other services, log level, config files.
  • Secret — sensitive values: passwords, API tokens, TLS certificates.

Both are injected into pods in one of two ways: as environment variables or as files in a mounted volume. The same image then runs in dev, staging and production with different config.

The key caveat: a Secret's values are only base64-encoded. Anyone who can read the Secret object can decode it in one command. Real protection comes from access control, encryption at rest and keeping plain secrets out of Git.

Creating ConfigMaps and Secrets

You can create them imperatively for quick tests:

kubectl create configmap app-config --from-literal=LOG_LEVEL=info --from-file=app.yaml
kubectl create secret generic db-credentials --from-literal=DB_PASSWORD=change-me

For real projects, keep manifests declarative. In a Secret, stringData lets you write plain values; Kubernetes encodes them on save:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  API_BASE_URL: "https://api.internal"
---
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  DB_PASSWORD: "change-me"

Do not commit a manifest like the Secret above to Git — see the section on protecting secrets.

Env vars or mounted files

containers:
  - name: app
    image: registry.example.com/app:1.4.0
    envFrom:
      - configMapRef:
          name: app-config
    env:
      - name: DB_PASSWORD
        valueFrom:
          secretKeyRef:
            name: db-credentials
            key: DB_PASSWORD
    volumeMounts:
      - name: config
        mountPath: /etc/app
        readOnly: true
volumes:
  - name: config
    configMap:
      name: app-config
Environment variablesMounted files
Best forSimple key-value settingsWhole config files, certificates
Updates when the object changesNo, only on pod restartYes, with a delay (not with subPath)
RiskCan leak into logs, crash dumps, child processesReadable by anything with file access in the container

A practical rule: small settings as env vars, files and certificates as volumes, and prefer files for secrets when your app supports it.

Handling config reloads

Changing a ConfigMap does not restart anything. What happens depends on how you consume it:

  • Env vars never change inside a running container. You need a new rollout.
  • Mounted volumes are updated by the kubelet eventually, but the app must re-read the file. Many apps only read config at startup.

Common approaches:

  1. Roll the deployment after a change: kubectl rollout restart deployment/app.
  2. Add a checksum annotation to the pod template (Helm and Kustomize make this easy). When the config changes, the hash changes and Kubernetes rolls the pods automatically.
  3. Use immutable, versioned ConfigMaps (app-config-v7, immutable: true) and point the deployment at the new name. Rollback becomes trivial.
  4. Watch the file in the app if it must pick up changes without restarts.

Why base64 is not encryption

Base64 only makes binary data safe to store as text. Decoding needs no key:

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

So the real safeguards are:

  • RBAC: restrict get, list and watch on Secrets. Note that list returns values too.
  • Encryption at rest in etcd (configured on the API server or enabled by your managed provider).
  • No plain Secrets in Git, and no secrets baked into images.
  • Limit who can create pods in a namespace: anyone who can run a pod there can mount its Secrets.

Getting secrets into Git safely

ToolHow it worksGood fit
Sealed SecretsYou encrypt a Secret with the cluster's public key; only the in-cluster controller can decrypt itGitOps teams without an external vault
External Secrets OperatorSyncs values from an external store (cloud secret managers, Vault) into Kubernetes SecretsTeams that already use a central secret store
SOPSEncrypts values in files with KMS or age keys; decrypted during deployMixed tooling, Helm or Kustomize pipelines

Common mistakes

  • Putting passwords into a ConfigMap "temporarily".
  • Committing base64-encoded Secrets and assuming they are hidden.
  • Expecting env vars to update after editing a ConfigMap.
  • Using subPath mounts and wondering why files never refresh.
  • Giving CI or developers cluster-wide read access to Secrets.

FAQ

Files are slightly safer because env vars are easier to leak through logs, debug output and child processes. Use env vars when the app only supports them, and keep logging of the environment disabled.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.