Container images should be the same in every environment. What changes (database hosts, feature flags, API keys) belongs in configuration supplied at runtime. Kubernetes provides ConfigMaps for ordinary settings, and Secrets for sensitive values. Both can be injected as environment variables or mounted as files.

ConfigMaps

Create from literals or files:

kubectl create configmap web-config \
  --from-literal=APP_ENV=production \
  --from-literal=LOG_LEVEL=info \
  --from-file=nginx.conf

Or declaratively:

apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
data:
  APP_ENV: production
  LOG_LEVEL: info
  app.yaml: |
    features:
      signup: true

Secrets

kubectl create secret generic web-secrets \
  --from-literal=DB_PASSWORD='s3cr3t-value' \
  --from-file=api-key=./api-key.txt
kubectl create secret tls web-tls --cert=tls.crt --key=tls.key

Important: Secret values are only base64-encoded, not encrypted. Anyone who can read Secrets in the namespace (or etcd backups) can decode them:

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

So protect them (see below), and never commit Secret YAML with real values to Git.

Use them in a pod

As environment variables:

spec:
  containers:
    - name: web
      image: myapp:1.8.3
      envFrom:
        - configMapRef: { name: web-config }
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef: { name: web-secrets, key: DB_PASSWORD }

As files:

      volumeMounts:
        - name: config
          mountPath: /etc/app
          readOnly: true
        - name: secrets
          mountPath: /run/secrets/app
          readOnly: true
  volumes:
    - name: config
      configMap: { name: web-config }
    - name: secrets
      secret: { secretName: web-secrets, defaultMode: 0400 }

Each key becomes a file (/etc/app/app.yaml, /run/secrets/app/DB_PASSWORD). Files are generally preferred for secrets: they don’t show up in process environments, or crash dumps.

Updating configuration

  • Mounted files update automatically (after a short delay), but your app must re-read them.
  • Environment variables never change in a running pod. Restart it:
kubectl rollout restart deployment/web

A common pattern: put a hash of the config in a pod annotation (Helm does this with checksum/config), so any config change triggers a rolling update automatically. See Helm.

Protecting Secrets

  1. RBAC: limit who can get/list Secrets. Many roles don’t need it. See Kubernetes security basics.
  2. Encryption at rest for etcd (EncryptionConfiguration on kubeadm clusters; k3s supports --secrets-encryption).
  3. Keep real values out of Git, with one of:
    • Sealed Secrets: encrypt with a cluster public key; safe to commit;
    • SOPS (with age or GPG) + a GitOps tool;
    • External Secrets Operator, syncing from Vault, a cloud secret manager, or Bitwarden Secrets Manager.
  4. Rotate secrets regularly, and immediately after a suspected leak.

See secrets management and Docker secrets.

Limits and tips

  • ConfigMaps and Secrets are limited to 1 MiB each. Store large files elsewhere.
  • Mark them immutable: true for config that never changes. It protects against accidental edits, and reduces API server load.
  • Namespaces scope them: a pod can only use ConfigMaps and Secrets in its own namespace.

Quick reference

Task Command
Create ConfigMap kubectl create configmap x --from-literal=K=V
Create Secret kubectl create secret generic x --from-literal=K=V
TLS secret kubectl create secret tls x --cert … --key …
Decode a value kubectl get secret x -o jsonpath='{.data.K}' | base64 -d
Apply env changes kubectl rollout restart deploy/web
Git-safe secrets Sealed Secrets / SOPS / External Secrets

Related: Kubernetes deployments and services and kubectl cheat sheet.

  • Kubernetes
  • Security
  • DevOps