Sign in
← Application patterns
10 min · easy · Lesson 3 of 5

12-factor configuration

Why config lives in the environment, what 'one artifact for every environment' means, and which 12-factor principles are about operations.

Why this matters

The same service lives in several environments: on a developer's laptop, on staging, in prod. The code is identical everywhere, but the database address, the keys, and the mode differ. If these differences are baked into the code or the image, every environment needs its own build, and what ships to prod isn't what was tested.

This lesson is about the principles that keep code and environment apart. They also explain why a service can run in ten copies and why its logs don't live in files.

Where 12-factor came from

The Twelve-Factor App is a set of twelve principles for writing services that are easy to operate in the cloud. Adam Wiggins wrote it based on experience at Heroku, where the authors shipped hundreds of applications. The principles became a shared language: when people say "a 12-factor app", they mean a service that's easy to configure, scale, and deploy.

Here we'll cover the four factors closest to DevOps:

FactorName in the originalGist
IIIConfigconfig lives in the environment
VBuild, release, runbuild and run are strictly separated
VIProcessesprocesses don't hold state
XILogslogs are a stream of events

Full list: 12factor.net.

Config in the environment

The factor that matters most here: config lives in the environment, not in the code. In the 12-factor sense, config is everything that changes between environments: the database address, credentials for external services, the hostname. It's passed through environment variables, not baked into files inside the build. The mechanics of variables were covered in the env-vars lesson; what matters here is the principle.

The sign of a violation is simple: if changing the database address between dev and prod requires rebuilding the artifact, the config isn't where it should be. The right setup is one image that runs in any environment, differing only in variables.

# one image, different environments
docker run -e DATABASE_URL=postgres://dev-db/app  myapp:1.4
docker run -e DATABASE_URL=postgres://prod-db/app myapp:1.4

What this looks like on a live container: the same command, the same image, only the variable changes.

docker run --rm -e APP_ENV=staging alpine:3.20.3 sh -c 'echo "env=$APP_ENV"'
env=staging

With -e APP_ENV=prod the same command prints env=prod. In Docker Compose you set the same thing with the service's environment: block:

services:
  api:
    image: myapp:1.4
    environment:
      DATABASE_URL: postgres://prod-db/app

Not every setting counts as config. Application routes or the links between modules are the same in every environment, and belong in the code. Only what differs from environment to environment goes into the environment.

12-factor has an honesty check: could you open the code in a public repository right now without exposing a single password? If not, config is mixed in with the code.

Variables are set one per setting. They aren't bundled into named sets like "development config" and "production config". Such sets multiply with every new environment (staging, qa, a demo for a client) and quickly drift apart.

One artifact for every environment

From this follows the build once, deploy anywhere principle. The artifact (the image) is built once, and that exact artifact is run through staging and prod. There's no separate "prod build": rebuilding means something other than what was tested on staging ships to prod.

In 12-factor this is factor V. A rollout splits into three stages:

  1. Build: the code is turned into a runnable artifact, for example an image.
  2. Release: the artifact is combined with a specific environment's config.
  3. Run: the release is started.

Code changes only at the build stage. Config is attached at the release stage, so the same image becomes different releases in different environments.

The version is pinned by a tag or digest, as covered in the registries lesson, and the differences between environments go into variables. That way staging and prod are guaranteed to run the same code.

Stateless processes

Another key factor: processes don't hold state. Anything that must survive a restart or be shared across multiple copies (sessions, uploaded files, carts) is moved out to external storage: a database, object storage, Redis. A process's memory and disk are fine only as a short-lived cache within a single request.

This is what horizontal scaling, covered in the load balancing lesson, rests on. If the copies are stateless, any request can be served by any of them, and copies can be added or removed freely. If a copy keeps a session in memory, the next request from the same user, landing on a different copy, won't see that session.

12-factor explicitly calls sticky sessions a violation. That is when the load balancer always sends a user to the same copy. They hide the problem: the copy restarted during a deploy, and the session was gone.

Logs as a stream

And one more factor: logs are a stream of events to stdout, not files managed by the application. The process just writes to stdout without buffering, and the environment (Docker, journald, a log collector) gathers, stores, and routes the logs. The application doesn't think about file rotation or where the logs end up.

That's why docker logs shows only what the process printed to stdout and stderr. If a service writes to a file inside the container, docker logs is empty, and the file disappears along with the container. Log levels are covered in a separate lesson.

Common mistakes

What you'll need for the tasks

In the next lesson we'll cover resilience to failures: retries, timeouts, and the circuit breaker.

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson app-patterns-03-config-12factor