Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Twelve-Factor App

The Twelve-Factor App is a methodology for building software-as-a-service applications, published in 2011 by Adam Wiggins based on Heroku’s platform experience. It distills twelve practices that make an app portable (runs the same on any environment), deployable (continuous delivery-friendly), scalable (horizontal by construction), and resilient (processes are disposable). Twelve-factor is not a framework or a product — it is the shared vocabulary of cloud-native design, and that is exactly why interviews use it: “is this service 12-factor?” compresses a dozen architecture questions into one. The canonical text is 12factor.net; this page summarizes each factor with the rule, the reason it exists, the smells of violating it, and its 2020s mapping.

Why It Still Gets Asked

Twelve-factor described, in 2011, what Docker (2013), Kubernetes (2014), and serverless (2014) later industrialized. Reading it today is reading the spec the cloud-native toolchain implements: containers made dependency isolation cheap, Kubernetes made disposability and concurrency the scheduler’s job, and GitOps turned build-release-run into a pull model. Interviewers deploy the vocabulary to test whether you understand why platforms work the way they do — or just operate them.

There is also a practical screening motive: a candidate who can name which factor a given architecture violates can be taught a team’s platform in a week; one who cannot will fight the platform. That is why “have you read 12-factor” appears in platform, SRE, and backend interviews even for shops that never mention it in the job ad.

The Twelve Factors at a Glance

#FactorOne-line ruleModern mapping
ICodebaseOne codebase, many deploysGit repo (or monorepo dir) as source of deploys
IIDependenciesExplicitly declare + isolateLockfiles, vendoring, containers
IIIConfigConfig in the environmentEnv vars, Secrets managers, External Secrets
IVBacking servicesAttach as resourcesConnection strings, k8s Services, managed clouds
VBuild-release-runStrict stage separationCI → registry image → GitOps deploy
VIProcessesStateless, share-nothingReplicas + external session store
VIIPort bindingApp is the web serverContainer listens on its own port
VIIIConcurrencyScale via process typesOne Deployment per type + HPA
IXDisposabilityFast start, graceful stopProbes, SIGTERM, termination grace
XDev/prod parityKeep environments similarContainers + IaC + preview envs
XILogsEvent stream to stdoutStructured JSON + sidecar collectors
XIIAdmin processesOne-off, same codebase/configJobs, CronJobs, release-phase hooks

Grouped by concern, the factors split into authoring-time rules (what you write), pipeline rules (how it ships), and runtime rules (how it behaves once live):

flowchart LR
    subgraph AUTHOR["Authoring-time factors"]
        F1["I. Codebase"]
        F2["II. Dependencies"]
        F3["III. Config"]
        F10["X. Dev/prod parity"]
    end
    subgraph PIPE["Pipeline factors"]
        F4["IV. Backing services"]
        F5["V. Build-release-run"]
        F12["XII. Admin processes"]
    end
    subgraph RUNTIME["Runtime factors"]
        F6["VI. Processes"]
        F7["VII. Port binding"]
        F8["VIII. Concurrency"]
        F9["IX. Disposability"]
        F11["XI. Logs"]
    end
    AUTHOR --> PIPE --> RUNTIME

The Factors, One by One

I. Codebase — one codebase, many deploys

Rule. A deployable app has exactly one version-controlled codebase; every deploy (staging, prod, a developer’s environment) is a run of some tagged/commit-pinned version of it. Why. Multiple deploys of one app must be versions of the same thing; a forked copy means fixes get applied twice and “what is prod running?” becomes unanswerable. Violation smells. Several repos each claiming to be the app; the staging copy diverging to hot-fix a bug; a repo deploying multiple unrelated services from one build. Modern mapping. Monorepos are compatible — the factor is per deployable unit, not per Git repo. In GitOps the repo becomes the source of deploys themselves (see GitOps).

II. Dependencies — explicitly declare and isolate

Rule. Never rely on implicit existence of system-wide packages; declare every dependency in a manifest (package.json, go.mod, requirements.txt) and isolate it (lockfiles, virtualenvs, container images). Why. A fresh checkout must build deterministically on any machine; implicit dependencies produce snowflake hosts that can never be rebuilt. Violation smells. READMEs saying “first apt-install X”; “works on the bastion because someone installed that Python lib in 2019”; builds that only succeed on one laptop. Modern mapping. Lockfiles made resolution reproducible; containers made isolation near-free (Docker) — the factor is so industrialized that its violations now mostly signal legacy systems.

III. Config — store config in the environment

Rule. Configuration that varies between deploys — credentials, database URLs, hostnames — lives in the environment (env vars), never in code or per-environment files committed to the repo. Why. The same build artifact must be promotable through environments; config-in-code means the artifact secretly differs per deploy and secrets leak into Git history. Violation smells. config/production.yml checked into the repo; builds that bake their target environment; credentials grep-able from Git history. Modern mapping. Env vars remain the substrate, but secrets managers (Vault, AWS Secrets Manager/SSM, Kubernetes Secrets with External Secrets Operators) inject values at runtime — 12-factor never distinguished config from secrets; modern practice does. Caveats below in the criticism section.

IV. Backing services — treat attached resources

Rule. Databases, queues, caches, mail servers, even third-party APIs are attached resources: addressed by a URL/credential, swappable without code change. Why. Swapping local Postgres for RDS, or a queue vendor for another, should be a config edit — coupling to service topology kills portability and elasticity. Violation smells. Code assuming “the DB is on this host”; service endpoints hard-coded per environment in source; an app that must be reconfigured to use a standby database. Modern mapping. Kubernetes Services and operators, cloud managed services, service brokers — the app sees a connection string and nothing else.

V. Build, release, run — strict stage separation

Rule. Three isolated stages: build turns code + dependencies into an immutable artifact; release combines the artifact with environment config; run launches processes in the execution environment. Every release is uniquely identified; rollback means launching the previous release. Why. No mutation of running systems; no builds happening on production hosts; every incident ends with “roll back” as a first-class operation. Violation smells. SSH-ing to prod to edit a file and restart; builds compiled on the app server; “rollback” meaning “find the old tarball”. Modern mapping. Container images are the build artifact, image digests are release IDs, and the run stage is a deployment rollout — canary and blue-green strategies (Progressive Delivery, Canary Releases) are release-stage policy, not code.

flowchart LR
    SRC["Git commit"] --> BUILD["BUILD: deps + compile + tests -> immutable artifact"]
    BUILD --> REL["RELEASE: artifact + env config, unique release ID"]
    REL --> RUN["RUN: launch processes in execution environment"]
    RUN -->|"code change"| BUILD
    RUN -->|"config change"| REL
    OLD["previous release"] -.->|"rollback"| RUN

VI. Processes — stateless and share-nothing

Rule. The app executes as one or more stateless processes; anything that must persist goes to a backing service (database, queue, cache). Nothing in process memory or local disk may be depended on by a later request. Why. Stateless processes can be replicated, rescheduled, and replaced freely — the precondition for horizontal scaling and zero-downtime deploys. Violation smells. Sessions in process memory behind a load balancer with sticky sessions; “restart the pod and users lose their carts”; uploads written to local disk and never replicated. Modern mapping. Kubernetes replicas assume it; sticky sessions are explicitly a smell; the session goes to Redis, files go to object storage (Kubernetes).

VII. Port binding — self-contained services

Rule. The app is the web server: it binds a port itself and speaks HTTP, rather than being injected into an external application server. Why. One service = one port means the execution environment can expose, load-balance, and route any app uniformly. Violation smells. Deploying a WAR into a shared Tomcat; requiring Apache mod_php; an app that only works behind a specific fronting server. Modern mapping. Containers expect self-binding; Kubernetes Services and ingress front the pod; sidecar proxies (Envoy) terminate TLS for the locally-bound port. Serverless is the reductio: the platform owns the port entirely.

VIII. Concurrency — scale out via the process model

Rule. Scale by adding processes of a given type (web, worker, scheduler), not by growing one giant multi-threaded process. Unix processes are the unit of management. Why. Process types scale independently — ten more web processes, two more queue workers — and crash isolation comes free. Violation smells. One process doing HTTP serving, cron jobs, and queue consumption; the only scaling story being “buy a bigger box”. Modern mapping. One Deployment per process type, each with its own HPA (Kubernetes); queue-driven burst offload to Lambda (serverless, Lambda) is concurrency as a managed primitive.

IX. Disposability — fast startup and graceful shutdown

Rule. Processes start in seconds and shut down gracefully on SIGTERM — finishing in-flight work, releasing locks — and must tolerate being killed at any moment. Why. Elastic scaling, rolling deploys, and crash resilience all assume processes are cattle: cheap to create, safe to destroy. Violation smells. Ten-minute boots with manual warm-up; dropped requests during deploys because SIGTERM is ignored; a worker that never re-leases its queue lock. Modern mapping. Readiness/liveness probes, preStop hooks, and termination grace periods in Kubernetes; spot/preemptible instances make disposability mandatory rather than optional.

X. Dev/prod parity — keep environments similar

Rule. Keep development, staging, and production as similar as possible: same backing services at the same versions, same tooling, small gaps between deploys and between developers. Why. Divergence is where “works in dev” bugs breed — SQLite vs Postgres, Windows vs Linux, Apache vs nginx each add a class of production-only failures. Violation smells. Dev on SQLite, prod on Postgres; “set up dev” being a two-day tribal ritual; staging refreshed twice a year. Modern mapping. Containers and IaC make parity cheap — the app image is identical, the infra is code (Terraform); preview environments spin parity up per-PR. Accepted partial deviation: parity of scale is expensive, so teams tier by risk while keeping topology identical.

XI. Logs — treat as event streams

Rule. A 12-factor app writes its log as an unbuffered event stream to stdout and never concerns itself with routing or storage; the execution environment captures and aggregates the stream. Why. Separates the app from log plumbing, so the same process runs unchanged whether logs go to a file, a collector, or stdout in a terminal. Violation smells. The app manages its own log files and rotation; log shipping configured inside the app; “logs are on that host’s disk, hope it doesn’t die”. Modern mapping. The biggest factor shift since 2011: logs are now structured (JSON with trace context), collected by sidecars/agents (Fluent Bit, Vector) into Loki/Elastic, and unified with metrics and traces under OpenTelemetry. See logging.

XII. Admin processes — run as one-off processes

Rule. Administrative and maintenance tasks — migrations, one-off scripts, REPL sessions — run as one-off processes in the identical environment: same codebase, same config, same release. Why. Admin code then evolves with the app and runs against the right environment; no snowflake ops box with its own assumptions. Violation smells. Running migrations from a laptop against prod; a “special” node where manual fixes are applied; admin scripts living in a wiki. Modern mapping. Kubernetes Jobs and CronJobs, ECS one-off tasks, Heroku release-phase hooks, and the standard “migration Job runs before rollout” CI pattern.

Common Violations Cheat Sheet

The fastest way to internalize the factors is to pair each with its most common production violation and its fix:

FactorCommon violationFix
I CodebaseStaging fork diverges to hotfixShared library + deploy from one repo
II DependenciesHost needs a hand-installed packageLockfile + container image
III Configconfig/production.yml in GitEnv injection / secrets manager
IV Backing servicesHard-coded DB host per environmentConnection strings from config only
V Build-release-runSSH-edit-restart on prodImmutable images + pipeline-only deploys
VI ProcessesSessions in pod memoryRedis-backed sessions, object storage for files
VII Port bindingWAR into shared TomcatApp binds its own port; front with ingress
VIII ConcurrencyOne process does web + cron + workersSeparate process types, scale each independently
IX DisposabilitySIGTERM ignored, requests droppedGraceful shutdown + readiness probes
X Dev/prod parityDev on SQLite, prod on PostgresSame services via containers/compose
XI LogsApp writes and rotates its own filesStructured stdout + sidecar/agent shipping
XII Admin processesMigrations run from a laptopMigration Jobs in the release pipeline

Code-review usage: when you spot a row’s violation, name the factor — “this in-memory session store is a factor VI violation under autoscaling” — and the conversation moves from taste to principle.

What Changed Since 2011

The methodology survives, but four factors changed meaning materially — the delta interviewers like to probe:

Factor2011 reading2020s reading
XI LogsWrite to stdout; platform pipes text filesStructured JSON with trace IDs; sidecar/agent collection; logs+metrics+traces unified (OpenTelemetry)
III ConfigEnv vars for everything that variesSplit: config vs secrets (managers, encrypted injection) vs feature flags (flag services as live config)
VI ProcessesHand-run processes behind a LBManaged replicas; serverless makes processes a platform primitive that scales to zero
V Build-release-runCI builds, ops releasesGitOps: release is a Git commit; supply-chain security (signed images, SBOMs) adds a stage 2011 never imagined

Disposability (IX) also hardened from advice into enforcement — probes and grace periods are now scheduler-configured contracts, not developer discipline.

Criticism and Legitimate Deviations

Twelve-factor is a model for stateless, request-driven web services; its critics mostly attack cases it never claimed:

  • Env vars don’t scale past a dozen keys. Flat, untyped, undocumented strings become a config dumping ground. Modern practice layers typed config libraries and hierarchical files over the env-var substrate — and admits that secrets in env vars leak (visible in child processes, crash dumps, debug endpoints), which is why secrets managers displaced them for credentials.
  • Parity is a cost dial, not a binary. Full prod-parity staging for a 5-person team can cost more than prod; tiering parity (identical topology, downsized scale) is a rational deviation.
  • Monoliths are not exempt, only factors are. A modular monolith can be perfectly 12-factor (one artifact, stateless replicas, streams to stdout); conversely, microservices built with sticky sessions and baked-in config violate it happily. The factors are orthogonal to service granularity — a point made in Microservices.
  • Stateful systems deviate structurally. Databases, ML training jobs, and data pipelines need local scratch, slow starts, and careful shutdown; forcing factor VI/IX on them produces ceremony, not reliability.
  • “One codebase” vs monorepo is a misreading, not a flaw — the factor scopes per deployable unit, and monorepos with per-service build roots satisfy it.

The interview move is to say which factors a given system deliberately breaks, and what it buys — that is senior-sounding because it treats 12-factor as a trade-off surface, not a religion.

12-Factor as System-Design Vocabulary

In system-design interviews the factors work as a checklist that structures an answer: stateless processes (VI) means sessions go to Redis and replicas scale behind an LB (VIII); config in environment (III) means the same image promotes through environments; logs as streams (XI) means a sidecar ships structured events while you answer the scalability question. When a prompt says “design a scalable web service”, factors VI, VII, VIII, and IX are the scaling story; when it asks about deploys, V and IX are zero-downtime; when it asks about environments, III, IV, and X are the answer. Dropping the names signals shared vocabulary with the interviewer’s platform team — see the API overview and platform engineering pages for adjacent vocabulary.

Interview promptFactors that answer itSibling pages
“Design a service that scales to N replicas”VI, VII, VIIIKubernetes, Serverless
“How do you ship config and secrets?”III, IVDocker, Kubernetes security
“Zero-downtime deploys / rollback”V, IXProgressive Delivery, Canary
“How would you debug or observe it?”XI, IXOpenTelemetry, On-Call
“Keep dev and prod consistent”X, II, ITerraform, GitOps

A final usage note: the factors also make good critique answers — when given an existing architecture to evaluate, walking the twelve factors is a legitimate review framework, and naming two or three concrete violations with fixes is a complete, well-bounded answer.

Cross-References

Interview Questions

Q1: What is the Twelve-Factor App methodology, and why does it still matter? A: A 2011 twelve-practice methodology from Heroku for portable, scalable, disposable SaaS apps — covering codebase, dependencies, config, backing services, build-release-run, stateless processes, port binding, concurrency, disposability, parity, logs, and admin processes. It matters because Kubernetes, containers, and serverless later implemented exactly these practices, so the vocabulary is the shared language of cloud-native design. Interviewers use it to check whether you understand why platforms behave as they do.

Q2: Which factors changed meaning the most since 2011? A: Logs went from stdout text streams to structured JSON with trace context, collected by sidecars and unified with traces/metrics. Config split into config, secrets (managers, encrypted injection), and feature flags. Processes became platform-managed replicas — serverless is concurrency and disposability as a managed primitive. Build-release-run became GitOps, where release is a Git commit and supply-chain signing added a stage 2011 never imagined.

Q3: “Stateless processes” — how do you reconcile that with sessions, websockets, or file uploads? A: Push all durable state to backing services: sessions to Redis, uploads to object storage, websocket connection state either externalized or accepted as per-pod ephemeral state behind consistent routing. The moment any request depends on “the same pod that handled my last request”, horizontal scaling and zero-downtime deploys break. For small apps, in-memory sessions with sticky LBs are a pragmatic deviation — but name it as one.

Q4: What are the limitations of “config in environment variables”? A: Env vars are flat, untyped, and undocumented, so they degrade into a dumping ground past a dozen keys; and secrets in env vars leak via child processes, crash dumps, and debug endpoints. Modern practice layers typed config loading on top and moves credentials to secrets managers injected at runtime. The 12-factor principle survives — config varies per deploy and lives outside the artifact — but the mechanism matured.

Q5: Express build-release-run in Kubernetes terms. A: Build = CI compiles and pushes an immutable image to a registry; release = a GitOps commit or deploy manifest pins that image digest plus environment config (ConfigMaps/Secrets); run = the controller rolls out pods, with rollback meaning “revert to the previous digest”. The strict separation is what makes rollback a pointer flip instead of an archaeology dig. Canary and blue-green are policies layered onto the release stage.

Q6: How do you achieve dev/prod parity when prod is serverless or managed services? A: You can’t run a faithful Lambda locally, so parity shifts from systems to contracts: run the same container image or handler code with emulators (LocalStack, containers for dependencies) in dev, and verify against the real service in an ephemeral preview environment. Teams tier parity by risk — identical code and config format, downsized scale. The failure mode to avoid is dev-only code paths that never run against prod semantics.

Q7: Where does 12-factor reasonably not apply? A: Stateful and long-running systems — databases, ML training, batch pipelines — need local scratch, slow starts, and careful shutdown, which factors VI and IX forbid; forcing them produces ceremony. Tiny teams may accept config files per environment, and monoliths can be fully 12-factor, so the factors are orthogonal to service granularity. The senior answer names which factors a system deliberately breaks and what that buys.

Q8: In a system-design interview, how do you use 12-factor without reciting a list? A: Use factor names as the connective tissue of the design: “processes are stateless so sessions go to Redis and we scale replicas (VI, VIII)”; “the container binds its own port behind a Service (VII)”; “SIGTERM handling with a grace period makes rolling deploys safe (IX)”; “structured logs stream to a sidecar (XI)”. It signals platform maturity in seconds and invites the interviewer to drill anywhere on ground you’ve already covered.

References