RizTech Academy logo
RizTech Academy
Container Orchestration with KubernetesLesson 4 of 630 min

ConfigMaps, Secrets and environment configuration

An application needs configuration — which greeting to show, which database to connect to, an API key — and that configuration differs between environments and must not be baked into the image. Kubernetes provides two objects for this: ConfigMaps for ordinary config and Secrets for sensitive values. Getting this right keeps one image running everywhere and keeps secrets out of your source code. This lesson is configuration on Kubernetes, verified against a live cluster.

Why config lives outside the image

Recall the container principle: you build one image and run that same image in dev, staging and production (the containers module). But those environments need different configuration — a different database URL, a different greeting, different keys. If configuration were baked into the image, you would need a different image per environment, defeating the whole point. So configuration is injected at run time, from outside the image — and the greetings app is written for exactly this, reading GREETING, PORT and REDIS_URL from its environment rather than hard-coding them. Kubernetes supplies those values via ConfigMaps and Secrets.

ConfigMaps: ordinary configuration

A ConfigMap holds non-sensitive configuration as key-value pairs. Here is the greetings ConfigMap, applied and verified:

apiVersion: v1
kind: ConfigMap
metadata:
  name: greetings-config
data:
  GREETING: "Namaste"
  PORT: "3000"

You then inject it into the pod. The simplest way is envFrom, which turns every key into an environment variable in the container — this is exactly what the greetings Deployment does:

      containers:
        - name: greetings
          image: greetings:1.0
          envFrom:
            - configMapRef:
                name: greetings-config     # GREETING and PORT become env vars in the pod

Apply both and the running app picks up the values — the verified response is {"message":"Namaste from greetings-..."}, where Namaste came straight from the ConfigMap, injected as the GREETING environment variable. Change the ConfigMap and restart the pods (or roll the Deployment) and the new value takes effect — same image, different config. You can also mount a ConfigMap as files (for a config file the app reads), but injecting as environment variables is the common case and the one to learn first.

Secrets: sensitive values

A Secret looks almost identical to a ConfigMap but is meant for sensitive data — passwords, API keys, tokens, TLS certificates. You reference it the same way (envFrom with secretRef, or individual env vars). The greetings secret, verified:

apiVersion: v1
kind: Secret
metadata:
  name: greetings-secret
type: Opaque
stringData:
  API_KEY: "..."     # stringData lets you write plain text; Kubernetes stores it base64-encoded

The crucial thing to understand — and a common, dangerous misconception:

  • Secrets are base64-encoded, not encrypted, by default. Base64 is not security; anyone who can read the Secret object can decode it instantly (kubectl get secret ... -o jsonpath='{.data.API_KEY}' | base64 -d reveals it — verified). So a Secret is not automatically safe just because it is a "Secret".
  • What Secrets do give you is: they are treated specially (kept out of logs, access-controlled separately via RBAC, can be encrypted at rest if the cluster is configured to, and can be sourced from real secret managers). Use a Secret rather than a ConfigMap for sensitive values so this special handling applies — but do not assume base64 alone protects anything.

The rule that keeps you safe: never commit secrets

Here is the practical discipline that matters most. Because a Secret manifest contains the (base64) value, you must never commit real secrets to Git. Committing a secret.yaml with a real API key puts that key in your repository history forever, readable by anyone with access — a serious and common leak. Instead:

  • Create secrets outside version control — with kubectl create secret generic ... --from-literal=..., or from a secrets manager (AWS Secrets Manager, HashiCorp Vault, Sealed Secrets, External Secrets), which inject the real values into the cluster without them ever touching Git.
  • Commit only the shape — a secret.yaml with placeholder values (as the greetings example does: API_KEY: "replace-me-in-a-real-secrets-manager") so the structure is documented, never the real value.
  • Keep secrets out of ConfigMaps — a ConfigMap is even less protected and often logged; sensitive values belong in a Secret, and real ones in a manager.

This mirrors the Dockerfile lesson's "never copy .env into an image" and the CI module's "secrets in pipelines, handled safely": the through-line is that secret values live in a secrets manager, and only references or placeholders live in your code. Get that habit right and you avoid the single most common — and most damaging — configuration mistake.

Check your work

Config lives outside the image because you run one image everywhere with different config per environment; injecting at run time (not baking in) keeps one image. The greetings app reads GREETING/PORT/ REDIS_URL from the environment for exactly this.

ConfigMap = non-sensitive key-value config; inject with envFrom.configMapRef (each key → an env var) or mount as files. Verified: GREETING: "Namaste" became the app's greeting. Change it + roll the pods = new config, same image.

Secret = for sensitive values (passwords/keys/tokens/TLS); referenced like a ConfigMap. Base64-encoded, NOT encrypted by default — anyone who can read it decodes it instantly; a Secret is not automatically safe. It does get special handling (kept from logs, separate RBAC, encryptable at rest, sourceable from managers).

Never commit real secrets: create them out of version control (kubectl create secret, or a secrets manager — Vault/AWS Secrets Manager/Sealed/External Secrets); commit only the shape with placeholders; keep secrets out of ConfigMaps. Through-line: values live in a secrets manager; only references/placeholders live in code.

Practice

  1. Explain why configuration must live outside the image, and how one image serves many environments.
  2. Write a ConfigMap and inject it into the greetings Deployment with envFrom; verify the value reaches the app.
  3. Change the ConfigMap value, roll the Deployment, and confirm the app picks up the new value.
  4. Explain why "base64-encoded" does not mean "secure", and demonstrate decoding a Secret value.
  5. Explain what special handling a Secret gets over a ConfigMap, and when to use each.
  6. Describe how you would manage a real API key so it never appears in Git, and what you would commit.

Official documentation

Next: health checks, self-healing and scaling.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship