The Twelve-Factor App

The twelve-factor methodology was published by Adam Wiggins and colleagues at Heroku in 2011, and its influence is hard to overstate: config in the environment, stateless processes, logs as streams, and disposability are now such default assumptions that most engineers apply them without knowing where they came from. Docker, Kubernetes, and every platform-as-a-service since encode these ideas as the path of least resistance.

Reading it now is worth doing for two reasons. Several factors are still exactly right and still routinely violated — configuration baked into images, processes that assume local disk persists, log files written to the filesystem. And a few have aged poorly or need qualification, because the assumptions of 2011 (a single-language runtime, a PaaS, no containers, no service mesh) aren't the assumptions of today.

The methodology is best treated as a set of well-argued defaults, not a compliance checklist. Deviating from a factor is fine when you understand what it was protecting against.

TL;DR

The Twelve Factors

I. Codebase — one codebase tracked in revision control, many deploys

Still right, with a caveat. The intent — one deployable unit's code lives in one place, and environments differ only by configuration — holds completely. The caveat is that a monorepo containing many apps doesn't violate this: the factor is about one app not being spread across repos, not about one repo per app.

II. Dependencies — explicitly declare and isolate

Still right, and now stronger. Modern practice adds a lockfile (which the original barely addressed), reproducible builds, and — increasingly — an SBOM. The 2011 version assumed a language package manager; containers made isolation the default rather than an effort.

III. Config — store config in the environment

The most important factor, and the most violated. The litmus test the original offers is still excellent: could you open-source the codebase right now without leaking any credential?

Where it has aged: environment variables alone are insufficient at scale. They're flat, untyped, size-limited, visible in process listings, frequently leaked into logs and crash reports, and awkward for structured configuration. Modern practice uses a secrets manager (Vault, AWS Secrets Manager, or Kubernetes secrets with a CSI driver) for credentials and typed config files or a config service for structured settings — with environment variables holding only the pointers.

IV. Backing services — treat as attached resources

Still right. This is what makes local development, ephemeral preview environments, and disaster recovery possible. The modern addition is that most backing services are now managed — and that a service you don't operate still needs a timeout, a retry policy, and a circuit breaker, which the original doesn't discuss.

V. Build, release, run — strictly separate stages

Still right and central. This is exactly how container images plus a deployment manifest work, and the immutability is what makes rollback a one-command operation instead of a recovery procedure. The original's insistence that you cannot modify code at runtime is the part still violated by anything that patches files in a running container.

VI. Processes — stateless and share-nothing

Still right, and the enabler for everything else. Statelessness is what makes horizontal scaling, rolling deploys, and instance replacement trivial. A local cache as an optimization is fine; a local cache the app's correctness depends on is not.

VII. Port binding — export services via port binding

Largely obsolete as stated, and its intent survives. In 2011 this was an argument against deploying a WAR into Tomcat. Today every runtime does this by default, and the port is usually assigned by the platform. The surviving principle is self-containment: the app doesn't require a specific external runtime container.

VIII. Concurrency — scale out via the process model

Right in intent, dated in specifics. The process model has been superseded by containers, pods, and serverless functions as the unit. The principle — scale by adding units, size each workload type independently — is exactly how Kubernetes deployments and autoscaling work. Where it needs qualification: async runtimes and goroutines mean in-process concurrency is often the right first answer, and the original underweights it.

IX. Disposability — fast startup, graceful shutdown

Still right and still commonly broken. Slow startup breaks autoscaling and makes deploys slow. Ignoring SIGTERM causes dropped requests on every deploy.

The subtlety the original doesn't cover: in Kubernetes you also need a preStop hook or a brief sleep, because the load balancer takes time to stop routing to a terminating pod. Handling SIGTERM correctly but exiting immediately still drops requests.

X. Dev/prod parity — keep environments as similar as possible

Still right, and now much more achievable. Containers and Docker Compose made same-services-locally realistic. The remaining honest gap is scale — you cannot replicate production data volume or traffic locally, which is why staging environments and progressive rollout still matter.

XI. Logs — treat logs as event streams

Still right, with a large modern addition. Write to stdout and let the platform handle it. What the original misses entirely is structured logging — JSON with consistent fields — and correlation IDs, both of which are the difference between logs you can query and logs you can only grep. See Logging.

XII. Admin processes — run admin tasks as one-off processes

Still right. Same image, same config, same code version. The modern shape is a Kubernetes Job or a task definition rather than heroku run.

What's Missing

A 2026 revision would add several things the original doesn't address, largely because the operational landscape changed:

Item XVI is the most significant omission. "Backing services are attached resources" tells you how to configure a dependency and nothing about what to do when it's slow or down — which is the dominant failure mode in a distributed system and the subject of a large body of practice that postdates the methodology.

Attempts at successors exist, including a "beyond the twelve-factor app" formulation that adds API-first and telemetry factors, and Kubernetes' own documented best practices. None has displaced the original, mostly because the original's virtue is being short enough that people actually read it.

Best Practices

Validate configuration at startup and fail fast

This is the single most valuable addition to factor III. A misconfigured deploy should fail at startup, visibly, not on the first request that happens to need the missing value.

Handle SIGTERM properly, including the load balancer lag

Stop accepting connections, drain in-flight work within the grace period, close pooled resources, exit zero. In Kubernetes add a preStop sleep so the endpoint is removed before you stop accepting. Getting this wrong drops requests on every single deploy.

Keep secrets out of environment variables

Environment variables leak — into process listings, crash dumps, error trackers, child processes, and debug endpoints. Put the reference in the environment and fetch the secret from a manager at startup, or mount it via a CSI driver. See Secrets Management.

Log structured JSON to stdout, with correlation IDs

Factor XI plus what it omits. One line per event, consistent field names, a request or trace ID on everything. This is what makes logs queryable rather than merely searchable.

Make startup fast

Autoscaling, rolling deploys, and crash recovery all depend on it. Do connection pool creation and warm-up lazily where possible, avoid blocking on non-critical dependencies at boot, and separate liveness from readiness so a slow-starting instance isn't killed while initializing.

Run migrations as a separate step from the same release

A Kubernetes Job or an init step using the identical image and config. Never from a developer's machine, and never automatically on every application boot — concurrent instances racing to migrate is a well-known way to corrupt a schema.

Add timeouts, retries, and circuit breakers to every backing service

The factor the methodology omits. Every network call needs a timeout, a bounded retry with jitter, and a circuit breaker on repeated failure. Without them, one slow dependency cascades into total unavailability.

Deviate deliberately, and write down why

A stateful service, a local cache the app depends on, or a config file instead of environment variables can all be correct. What matters is that it's a considered decision with a recorded rationale rather than an accident. An ADR is the right home for it.

Common Mistakes

Config baked into the artifact

Assuming local disk persists

In-memory session state

Ignoring SIGTERM

Writing log files

Migrations on application boot

Different services locally and in production

FAQ

Is the twelve-factor app still relevant?

Yes, as well-argued defaults rather than a checklist. Factors III (config), VI (stateless), IX (disposability), X (parity), and XI (logs) are still essential and still commonly violated. Factor VII (port binding) is obsolete as stated because every runtime does it. Factor VIII's process model has been superseded by containers and pods while its principle stands. The gap worth knowing about is resilience — timeouts, retries, circuit breakers — which the methodology simply doesn't cover.

Should config really only be environment variables?

No, and this is the factor that has aged least well. Environment variables are flat, untyped, size-limited, and leak into process listings, crash reports, and child processes. The modern shape: environment variables for pointers (a secret ARN, a config path), a secrets manager for credentials, and a typed validated schema for structured settings. The underlying principle — config is not code and varies by deploy — is entirely intact.

Does a monorepo violate factor I?

No. The factor says one app shouldn't be spread across multiple repositories; it says nothing about one repository containing several apps. A monorepo with clear package boundaries, per-app builds, and independent deploys satisfies the intent completely — each app has one codebase, and that codebase happens to share a repository with others.

Are stateful services always wrong?

No. Databases, caches, message brokers, and stateful stream processors all necessarily hold state, and Kubernetes StatefulSets exist because the need is legitimate. The factor is about application processes — the ones you scale horizontally and replace freely. Where you do build a stateful service, you're accepting responsibility for replication, failover, and data durability, which is the work the factor was helping you avoid.

How does this apply to serverless?

Serverless enforces most of it. Statelessness, disposability, fast startup, config injection, and logs-to-stdout are all mandatory rather than recommended. What changes: the process model (factor VIII) becomes invocation-based, cold start makes fast startup critical rather than merely good, and connection pooling to a database becomes a real problem because there's no long-lived process to pool in. See Serverless Patterns.

What should I read instead, or as well?

The original is short and worth reading in full — an hour, and it's well written. Alongside it: the SRE and reliability literature for the resilience gap, Kubernetes' production best practices for the operational specifics, and Release It! for the failure modes the methodology doesn't address. The twelve factors tell you how to build something deployable; they don't tell you how to keep it up when a dependency degrades.

Related Topics

References