Externalized Configuration
Context
Section titled “Context”You now package each service as an immutable image and promote that exact image from development through staging to production. But a service needs different settings in each of those places: a different database URL, a different message-broker address, different credentials, and feature flags that are on in staging and off in production.
If those settings are baked into the image, you no longer have one artifact — you have one image per environment, each built from the same code but with different values compiled in. The image you tested in staging is not the image you run in production. Worse, baking in a database password means your secret is now sitting in an image layer in a registry, readable by anyone who can pull it.
Problem
Section titled “Problem”You want a single built image to run unchanged in every environment, so that what you tested is exactly what you ship. That means the environment-specific values — endpoints, credentials, feature flags — cannot live inside the image; they must come from outside it, supplied when the service starts in a particular place. And the sensitive ones, like passwords and tokens, must be handled as secrets, not as plain text sitting in an image or a repository.
So the forces are: one immutable, environment-agnostic artifact, but correct, environment-specific, and secret-safe settings at runtime.
Solution
Section titled “Solution”Adopt externalized configuration: the image contains only code and sensible defaults, and every environment-specific value is injected at runtime. The service reads its configuration when it starts (and, for some sources, while it runs) rather than from constants compiled into the build.
There are three common injection mechanisms, often used together:
- Environment variables — the platform sets values like a database URL into the service’s environment at launch. Simple, universal, and the most common form.
- A configuration server — the service fetches its settings from a central config service at startup, which keeps configuration for the whole fleet in one governed place and can push updates.
- A secrets store — sensitive values live in a dedicated secrets manager and are mounted or fetched at runtime, never written into the image or source control.
The same image, started in staging, picks up staging’s values; started in production, it picks up production’s. The artifact is identical; only the injected configuration differs.
flowchart LR
subgraph Sources[Config sources per environment]
Env[Environment variables]
CfgSrv[Config server]
Sec[Secrets store]
end
Img[Immutable image: code + defaults] --> Svc[Service at startup]
Env -->|inject| Svc
CfgSrv -->|fetch| Svc
Sec -->|mount / fetch| Svc
Svc --> Run[Running service, configured for this environment] Example
Section titled “Example”The image carries no environment values. The deployment for each environment injects them: plain settings come from environment variables, while the database password is referenced from a secret so it never appears in the manifest or the image.
# Production deployment — same image as staging, different injected values.env: - name: SERVICE_ENV value: production - name: ORDER_DB_URL value: postgres://orders-db.prod.internal:5432/orders - name: BROKER_URL value: kafka://broker.prod.internal:9092 - name: FEATURE_NEW_CHECKOUT value: "false" - name: ORDER_DB_PASSWORD valueFrom: secretRef: name: order-db-credentials key: passwordThe service reads these on startup. Promoting to production means deploying the same image with this manifest instead of staging’s; the password is resolved from the secrets store at launch and never lands in the image layers.
Resulting context
Section titled “Resulting context”What you gain:
- One image, every environment. A single immutable artifact is promoted unchanged from staging to production, so the thing you tested is exactly the thing you run — no per-environment rebuilds.
- Safer secrets. Credentials live in a secrets store and are injected at runtime, keeping them out of image layers and source control.
- Centralized, changeable settings. With a config server, configuration for the whole fleet lives in one governed place, and some values can be changed without rebuilding or even redeploying.
What it costs you:
- Secret management is now a real system. You must operate a secrets store, control access to it, and rotate credentials — externalizing config does not make secrets easy, it makes them explicit.
- A startup-time dependency. If the service fetches config from a server or secrets store, that source becomes something the service depends on to start; it must be highly available and have sensible fallbacks.
- Config sprawl and drift. Settings scattered across env vars, a config server, and secrets can drift between environments. You need conventions, validation at startup, and a single source of truth per concern.
Related patterns
Section titled “Related patterns”- Service per Container — externalized configuration is what makes the immutable container image truly environment-agnostic.
- Microservice Chassis — the chassis is typically the component that reads and validates the injected configuration.
- Serverless Deployment — functions take their settings the same way, from injected values rather than baked-in constants.