Skip to main content

Lesson: ConfigMaps & Secrets

What you'll learn

  • Why configuration belongs outside your container image.
  • How a ConfigMap stores non-secret settings, as env vars or mounted files.
  • How a Secret stores sensitive values, and the base64 caveat.
  • When to inject config as environment variables vs mounted files.

Skill gained: you can keep settings and credentials out of your image and feed them to Pods cleanly.

The lesson

A good container image is generic: the same myapp:1.0 image should run in dev, staging, and production. The thing that changes between them is configuration — database URLs, feature flags, API keys. So you must keep config out of the image and inject it at run time. Kubernetes gives you two objects for that: ConfigMap (non-secret) and Secret (sensitive).

1. Why config lives outside the image

If you bake the database URL into the image, you need a different image per environment, and changing a setting means a rebuild. Instead, build once and inject config at deploy time:

        myapp:1.0  (one image, no config baked in)
           |
   +-------+--------+
   v                v
 dev ConfigMap   prod ConfigMap   <- different values, same image
 dev Secret      prod Secret

This is the "build once, run anywhere" principle. Your image carries code; the cluster carries config.

2. ConfigMap — non-secret settings

A ConfigMap is a set of key/value pairs for non-sensitive configuration.

apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
data:
  APP_GREETING: "Hello from the lab"
  LOG_LEVEL: "info"
  app.properties: |
    timeout=30
    retries=3
kubectl apply -f web-config.yaml
kubectl get configmap web-config -o yaml

3. Two ways to consume it: env vars vs mounted files

As environment variables — good for a handful of simple values your app reads from the environment:

spec:
  containers:
    - name: web
      image: busybox:1.36
      command: ["sh", "-c", "echo $APP_GREETING; sleep 3600"]
      env:
        - name: APP_GREETING
          valueFrom:
            configMapKeyRef:
              name: web-config
              key: APP_GREETING
      # or pull ALL keys at once:
      envFrom:
        - configMapRef:
            name: web-config

As mounted files — good for whole config files (nginx.conf, app.properties). Each key becomes a file in the mount directory:

spec:
  volumes:
    - name: config
      configMap:
        name: web-config
  containers:
    - name: web
      image: nginx:1.27
      volumeMounts:
        - name: config
          mountPath: /etc/web      # creates /etc/web/app.properties, /etc/web/LOG_LEVEL, ...
ConfigMap keys -> mounted as files       ConfigMap keys -> env vars
/etc/web/app.properties                  APP_GREETING=Hello...
/etc/web/LOG_LEVEL                        LOG_LEVEL=info

Rule of thumb: a few scalar settings -> env vars; a whole file the app expects on disk -> mounted volume.

4. Secret — sensitive values

A Secret is shaped like a ConfigMap but is meant for sensitive data: passwords, tokens, TLS keys. Kubernetes treats Secrets a bit more carefully (kept in memory on nodes, not written to disk by kubelet, separate RBAC) and tools avoid printing them.

The easiest and safest way to create one is to let kubectl do the encoding — never hand-edit base64:

kubectl create secret generic db-secret \
  --from-literal=DB_USER=appuser \
  --from-literal=DB_PASSWORD=<REDACTED>

Consume it just like a ConfigMap, with secretKeyRef (env) or a secret volume (files):

spec:
  containers:
    - name: web
      image: busybox:1.36
      command: ["sh", "-c", "echo using $DB_USER; sleep 3600"]
      env:
        - name: DB_USER
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: DB_USER
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: DB_PASSWORD

5. The base64 caveat — Secrets are NOT encrypted

This trips everyone up, so read it twice. When you look at a Secret, its values are base64-encoded, which looks scrambled:

kubectl get secret db-secret -o yaml
# data:
#   DB_PASSWORD: c2VjcmV0cGFzcw==     <- this is NOT encryption

Base64 is just encoding — anyone can decode it instantly:

echo 'c2VjcmV0cGFzcw==' | base64 -d   # prints the plaintext

So a Secret only adds encoding and handling care, not real secrecy by itself. Practical consequences for the internship:

  • Never put real secrets in YAML files you commit to Git. Create them with kubectl create secret (or a proper secrets manager) instead.
  • Anyone who can read Secrets in a namespace can read your passwords. Treat read access as sensitive.
  • In docs and examples, write secret values as <REDACTED>, never the real thing.

6. Changing config

If you edit a ConfigMap/Secret consumed as env vars, running Pods do not see the change — you must restart them:

kubectl apply -f web-config.yaml
kubectl rollout restart deploy/web     # recreate Pods so they pick up new env

Values consumed as mounted files are eventually updated in place (after a short delay), but most apps only read config at startup, so a rollout restart is the reliable way to apply changes either way.

kubectl describe pod <pod>             # see which ConfigMaps/Secrets are mounted/injected
kubectl exec <pod> -- env | sort       # verify env vars actually landed

Dig deeper

Search terms

  • kubernetes configmap vs secret
  • kubernetes configmap as environment variable
  • kubernetes mount configmap as file
  • kubernetes secret base64 not encrypted
  • kubectl rollout restart to reload config

Check yourself

  1. Why should configuration not be baked into the container image?
  2. Give one case where you'd inject config as env vars and one where you'd mount it as files.
  3. How should you create a Secret so you don't hand-edit base64?
  4. Is a Secret's value encrypted? What is base64, and what does that mean for your Git repo?
  5. After editing a ConfigMap used as env vars, what must you do for running Pods to see the change?