Home/Engineer/Docker / Containers/Kubernetes ConfigMaps and Se…
Docker / ContainersIntermediatelinuxkubernetesconfigmapsecret

Kubernetes ConfigMaps and Secrets: Injecting Configuration, Reloading on Changes, and Protecting Sensitive Data

Separate configuration and sensitive data from container images with ConfigMaps and Secrets, covering practical patterns such as env/volume injection, auto-reload, and encryption.

Contents
  1. What Are ConfigMaps and Secrets?
  2. Creating a ConfigMap
  3. Creating a Secret
  4. Injecting into Containers — Environment Variables
  5. Injecting into Containers — Volume Mounts
  6. Automatic Rolling Restart on Config Changes
  7. Hardening Security — Actually Protecting Secrets
  8. Verification and Debugging
  9. Summary

What Are ConfigMaps and Secrets?

A core 12-factor principle is to "separate config from code (the image)." Kubernetes implements this with two resources.

  • ConfigMap: Stores non-sensitive configuration (app options, URLs, feature flags, and so on) as key-value pairs
  • Secret: Stores sensitive data such as passwords, tokens, and certificates (base64 encoding + access control)

Both can be injected into containers as environment variables or volume files, so the same image can be reused across dev/stage/prod with different configuration.

A Secret's default encoding is base64, not encryption. Anyone can decode it, so do not assume "it's a Secret, so it's safe." Real protection comes from RBAC + etcd encryption + external secret-management tools.


Creating a ConfigMap

Imperative (literals / files)

Bash
# -
kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=info \
  --from-literal=MAX_CONN=100

# file (--from-file)
kubectl create configmap nginx-conf --from-file=./nginx.conf

# .env (--from-env-file)
kubectl create configmap app-env --from-env-file=./app.env
YAML
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  MAX_CONN: "100"
 # file settings
  application.yaml: |
    server:
      port: 8080
    feature:
      newUI: true
Bash
kubectl apply -f configmap.yaml
kubectl get cm app-config -o yaml

Creating a Secret

Bash
#
kubectl create secret generic db-secret \
  --from-literal=DB_USER=admin \
  --from-literal=DB_PASS='S3cr3t!23'

# TLS certificate
kubectl create secret tls web-tls --cert=tls.crt --key=tls.key

#
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=ci --docker-password='token'

When writing YAML, values must be base64. To enter plaintext, use stringData.

YAML
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
stringData: # → save when base64
  DB_USER: admin
  DB_PASS: "S3cr3t!23"

The main Secret types are as follows.

typePurpose
OpaqueArbitrary key-value pairs (default)
kubernetes.io/tlsTLS certificate/key
kubernetes.io/dockerconfigjsonRegistry credentials
kubernetes.io/service-account-tokenSA token

Injecting into Containers — Environment Variables

Injecting individual keys

YAML
spec:
  containers:
  - name: app
    image: myapp:1.0
    env:
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: LOG_LEVEL
    - name: DB_PASS
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: DB_PASS

Injecting everything at once (envFrom)

YAML
    envFrom:
    - configMapRef: { name: app-config }
    - secretRef: { name: db-secret }

Values injected as environment variables are baked in once when the Pod is created. Updating a ConfigMap does not refresh env vars in already-running Pods. You need a rolling restart for changes to take effect.


Injecting into Containers — Volume Mounts

When you mount a ConfigMap/Secret as files, unlike environment variables, mounted files update automatically when the ConfigMap changes (with a delay of tens of seconds).

YAML
spec:
  containers:
  - name: app
    image: myapp:1.0
    volumeMounts:
    - name: config-vol
 mountPath: /etc/app # /etc/app/application.yaml
      readOnly: true
    - name: secret-vol
      mountPath: /etc/secret
      readOnly: true
  volumes:
  - name: config-vol
    configMap:
      name: app-config
 items: #
      - key: application.yaml
        path: application.yaml
  - name: secret-vol
    secret:
      secretName: db-secret
 defaultMode: 0400 #
MethodAuto-updateBest for
Environment variables (env/envFrom)NoSimple values, 12-factor apps
Volume mountYes (file refresh)Config files, certificates, hot-reload apps

Automatic Rolling Restart on Config Changes

Environment-variable injection does not apply automatically, so you must roll the Deployment when config changes. A common pattern is to stamp a hash of the ConfigMap contents into an annotation.

YAML
spec:
  template:
    metadata:
      annotations:
 # → Pod → when
        checksum/config: "a1b2c3..."

To roll immediately by hand, use:

Bash
kubectl rollout restart deployment/myapp

Creating an immutable ConfigMap (immutable: true) via kubectl edit or apply prevents accidental changes and reduces API server load. To change it, you must delete and recreate it.


Hardening Security — Actually Protecting Secrets

1) Encryption at rest in etcd

YAML
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets"]
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: <base64-32byte-key>
  - identity: {}

Adding --encryption-provider-config to the API server startup flags stores Secrets encrypted in etcd.

2) Restrict access with RBAC

YAML
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
  namespace: prod
rules:
- apiGroups: [""]
  resources: ["secrets"]
 resourceNames: ["db-secret"] # Secret
  verbs: ["get"]

3) Integrate external secret management

In production, do not store plaintext Secrets in git. Prefer the following:

  • External Secrets Operator: Sync from AWS Secrets Manager, Vault, and similar stores
  • Sealed Secrets: Commit ciphertext to git; decrypt only inside the cluster
  • SOPS: Encrypt files while managing keys with KMS

Verification and Debugging

Bash
# Secret check
kubectl get secret db-secret -o jsonpath='{.data.DB_PASS}' | base64 -d; echo

# Pod check
kubectl exec deploy/myapp -- env | grep -E 'LOG_LEVEL|DB_'

# settings file check
kubectl exec deploy/myapp -- cat /etc/app/application.yaml
SymptomCause
CreateContainerConfigErrorReferenced ConfigMap/Secret/key does not exist
Empty string valueTypo in key name or case mismatch
Files not visiblemountPath overwrites an existing directory (replaced entirely)

Summary

ItemConfigMapSecret
TargetNon-sensitive configPasswords/tokens/certificates
StoragePlaintextbase64 (not encryption)
Injectionenv / envFrom / volumeSame
Auto-updateVolume mount onlyVolume mount only
SecurityRBACRBAC + etcd encryption + external tools
Applying changesrollout restartSame

Separating config from code is the essence of ConfigMaps and Secrets. Use volume mounts when you need hot reload, environment variables for simple values, and remember that Secrets are only base64—back them with etcd encryption, RBAC, and external secret management for real protection.

#kubernetes#configmap#secret#configuration
Editorial note

AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us

Topic-related official docs (auto-linked · not per-claim sources)Kubernetes documentation ↗Docker documentation ↗OWASP documentation ↗

Questions & answers

Ask a question about this guide. We'll review and reply.