Skip to main content

Config & secrets: feeding your app safely

Your app needs settings — a database address, a feature flag, an API key — and these differ between staging and production. Where do they go? A tempting but wrong answer is "bake them into the image." This lesson explains why configuration must live outside the image, and how Kubernetes supplies it through two objects: ConfigMaps for ordinary settings and Secrets for sensitive ones.

The durable principle: separate config from code

There's a foundational software principle here, often summarized as "separate configuration from code." Your container image (the code) should be built once and run unchanged in every environment. What varies between environments — the database URL, which is different in staging vs production — is configuration, and it should be injected at runtime, not compiled in.

Why this matters concretely:

  • One image, every environment. If the database URL were baked into the image, you'd need a different image for staging and production — breaking the "build once, run anywhere" promise and the guarantee that staging matches prod. With config injected, the same immutable image runs everywhere, just fed different settings.
  • Change settings without rebuilding. Flip a feature flag or point at a new database by changing config, not by rebuilding and redeploying an image.
  • Keep secrets out of images. An API key baked into an image is permanently embedded in that artifact — copied to every registry and machine, impossible to rotate without a rebuild, and leaked to anyone who can read the image. That's a serious security failure.

:::note This connects straight to Chapter 1 and 2 "Separate config from code" is the application-level echo of patterns you've already seen: immutable images (Chapter 4), reproducible environments differing only by inputs (the Terraform modules of Chapter 3), and least-privilege secret handling (the IAM lesson of Chapter 2). Same durable instinct: keep the artifact constant, vary the inputs, and never embed secrets. :::

ConfigMaps: non-sensitive settings

A ConfigMap is a Kubernetes object that holds non-sensitive configuration as key-value pairs — things like a log level, a feature flag, or a public service URL. You define it once, then inject its values into your pods, typically as environment variables the app reads, or as files mounted into the container.

apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
FEATURE_NEW_CHECKOUT: "true"
API_BASE_URL: "https://api.internal.example.com"

Your deployment then references this ConfigMap, and Kubernetes makes those values available inside the pods. Change the ConfigMap and re-roll the deployment, and the app picks up the new settings — no image rebuild. Staging and production use the same image with different ConfigMaps.

Secrets: sensitive values, handled distinctly

A Secret is the same idea as a ConfigMap but for sensitive data — passwords, API keys, certificates, database credentials. It's a separate object type on purpose, so that sensitive values are handled differently from ordinary config: access to Secrets can be restricted via IAM/RBAC, they can be encrypted at rest, and they're kept out of plain config.

apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
# values are base64-encoded (NOTE: encoding is NOT encryption — see below)
DB_PASSWORD: c3VwZXItc2VjcmV0

You inject Secret values into pods the same ways as ConfigMaps (env vars or mounted files), but you treat the object itself as sensitive.

:::tip The honest truth about Kubernetes Secrets A dangerous beginner misconception: base64 encoding is not encryption. By default, a Kubernetes Secret's value is merely base64-encoded — trivially reversible, not secured. Anyone who can read the Secret object reads the password. To genuinely protect secrets you must do more: enable encryption at rest for Secrets in the cluster, lock down who can read them with least-privilege RBAC, and for serious needs use an external secrets manager (a dedicated vault service — AWS Secrets Manager, HashiCorp Vault, etc.) that integrates with the cluster. Knowing this limitation is what separates a safe setup from a leaked one. (The specific managers are dated; the principle — encode ≠ encrypt, use real secret management — is durable.) :::

How it fits together

Immutableimage\n(built once)PodConfigMap\n(settings)Secret\n(credentials)Running app\n= image+ injected configenv vars / filesenv vars / files

The running application is the immutable image plus the injected config and secrets. Swap the ConfigMap and Secret, and the same image becomes the staging app or the production app. That's the separation principle delivering exactly what it promised.

Why it matters

Configuration and secrets must live outside the image so one immutable artifact runs in every environment, settings change without rebuilds, and credentials never get baked into a distributable image. Kubernetes provides ConfigMaps for non-sensitive settings and Secrets for sensitive ones, both injected into pods as environment variables or files. The critical caveat: Kubernetes Secrets are only base64-encoded by default — that's encoding, not encryption — so real protection requires encryption at rest, least-privilege access, and often an external secrets manager. "Separate config from code" is a durable principle that ties this chapter back to immutable images, reproducible environments, and least-privilege IAM. One piece of the picture remains: the cluster's brain — the control plane — that makes all this reconciliation actually happen.

Where this leads: secret management and least-privilege access deepen in Cloud Security (Chapter 8) and the Modern Security Engineer Guide.

Next: The control plane →