Be Software Architecture Wise
Be Wise Apps

Leer en español · Lire en français · Auf Deutsch lesen

Section 10.3 of 10.8 · Intermediate · 20 min read

The twelve-factor app and cloud-native principles

Video lesson

5 min · narrated, with captions
Listen only (audio)
Read the transcript

Kubernetes and every modern platform assume that applications behave in certain ways. In this lesson you'll learn those rules, the twelve factors, and how to audit a real service against them.

Think of a chain of takeaway kitchens. A good recipe card works in any branch: it lists every ingredient, and says nothing about which branch it's in. Any cook can pick it up mid-shift, because nothing important is kept in one cook's head. And when the menu changes, a new card is printed and numbered, not scribbled on. The twelve-factor app is that kind of recipe card for software.

In 2011, Adam Wiggins, a co-founder of Heroku, published The Twelve-Factor App, distilling what his team had learned from watching a very large number of applications on its platform. It came before Docker and Kubernetes, yet it describes almost exactly the contract those platforms later enforced. And it's about an application's relationship with its platform, not its insides: a modular monolith can be perfectly twelve-factor.

Let's take the factors in groups. Factor one: one codebase in version control, deployed to many environments. Factor two: every dependency declared, so builds are reproducible on any machine. Factors three and four keep URLs, credentials and backing services outside the code, found through configuration. A simple test: could the code be made public today without exposing a single credential? Factor five, build, release, run, keeps those stages apart. We'll come back to it in a moment. Six to nine make scaling safe. Processes are stateless and share nothing. The app serves by listening on a port. You scale by running more processes of each type. And processes start fast and shut down gracefully. Ten to twelve reduce surprises: keep development and production similar, write logs as a stream to standard output, and run one-off tasks, such as migrations, as separate processes using the same release.

Here's factor five. The build stage turns a commit into an executable artefact, today usually a container image identified by its digest. It's built once. The release stage combines that image with one environment's configuration, and gives the result an identity, such as release forty two. The run stage starts processes from a release. Releases are immutable: any change of code or configuration produces a new release, never an edit to an old one. So rolling back means simply running release forty one again, with no rebuild. Nobody edits files on a server, so servers don't drift, and what passed testing is exactly what runs.

Let's audit a real service: the food delivery platform's five year old restaurant onboarding service, on two hand-built virtual machines. The team wants to move it to the container platform, and the audit finds nine problems. First, the production database password is in a file in the repository. Anyone who can read the code has it. Rotate it now, and move it to the secrets manager. Priority one. Second, uploaded menus and logos are written to a machine's local disk. They vanish when a request lands on the other machine, and they block any move to disposable containers. Also priority one. Third, sessions are kept in memory, so every deployment logs restaurant staff out in the middle of a form. Priority two. So are deploying by logging in to each machine and pulling code, migrations run by both instances at start-up, and hard kills during deploys: no rollback, racing migrations and half-saved forms. Log files, a nightly job on one machine only, and SQLite on developers' laptops can follow later, at priority three. Notice the order: risk first, because an exposed credential is a live incident, then whatever blocks the target platform, then reliability, then convenience.

Some advice has aged. Environment variables leak into logs, crash reports and child processes, so secrets now belong in a secrets manager or, better, workload identity, where the platform gives each service short-lived credentials. Stateless doesn't mean the system has no state. It means processes don't keep it: state moves to backing services designed to keep it. And current practice adds four themes: design to be killed at any moment, never patch what's running, emit logs, metrics and traces, and build security in from the start.

The full section has a table of all twelve factors with their modern caveats, and the complete audit. Next, in Section ten point four, we apply the same discipline to the infrastructure itself. See you there.

What you need to know

Where the twelve factors came from

In 2011 Adam Wiggins, a co-founder of Heroku, published The Twelve-Factor App, a short web document distilling what the Heroku team had learned from watching a very large number of applications being built, deployed and scaled on its platform. The twelve-factor app methodology describes applications that set themselves up from declarative, automated steps, have a clean contract with the platform beneath them so that they can run anywhere, keep development and production close enough for continuous deployment, and can scale out without redesign.

The document predates Docker (2013) and Kubernetes (2014), yet it describes almost exactly the contract those platforms later enforced: an application is a set of stateless processes, built into an immutable artefact, configured from outside, writing logs to a stream and talking to everything else over the network. That is why it remains the common vocabulary of cloud-native application design. In November 2024 Heroku announced that the methodology was now open source, maintained by a community of contributors from several companies, with the aim of refreshing it for current practice; its original twelve factors remain the reference point.

The factors are about the application's relationship with its runtime platform, not about its internal architecture. A modular monolith can be perfectly twelve-factor; a set of microservices can violate every factor. They complement the deployability and operability concerns of Chapter 2 and the scaling and statelessness principles of Chapter 9.

The twelve factors, with modern caveats

FactorWhat it meansWhy it mattersModern caveat
I. CodebaseOne codebase in version control per app, deployed to many environments.Every environment runs some version of the same code; no forks per customer or per environment.A monorepo holding several apps is fine if each app has a clear boundary; the anti-pattern is copy-and-modify forks.
II. DependenciesDeclare every dependency explicitly and isolate the app from the host system.Builds are reproducible on any machine; nothing relies on a package someone installed by hand.Lock files and images make this the default; pinned dependencies still need regular updating and vulnerability scanning.
III. ConfigKeep everything that varies between deploys (URLs, credentials, feature settings) out of the code, in the environment.One artefact runs in every environment; secrets never enter the repository.Environment variables leak into logs, crash reports and child processes; secrets are better read from a secrets manager or mounted files.
IV. Backing servicesTreat databases, queues, caches and external APIs as attached resources located by configuration.A local database can be replaced by a managed one, or a failed one by a restored copy, by changing a URL.Swaps are rarely seamless when semantics differ; isolate differences behind ports and adapters.
V. Build, release, runSeparate building an artefact, combining it with config into a release, and running it.Releases are immutable and numbered; any release can be rerun; nobody edits code on a server.Today: build once in CI, store the image by digest, record releases in Git or the deployment tool.
VI. ProcessesRun the app as one or more stateless processes that share nothing; keep state in backing services.Any copy can serve any request, so copies can be added, removed and replaced freely.Sticky sessions hide the problem rather than fix it; local disk is only a scratch area.
VII. Port bindingThe app is self-contained and exports its service by listening on a port.The platform routes traffic to it without injecting it into a separate web server.Universal now; queue consumers have no port, and service meshes may sit in front of it.
VIII. ConcurrencyScale out by running more processes, with different process types for different work (web, worker).Each kind of work scales independently to its own load.Autoscalers do this automatically, including on queue length; threads within a process still matter.
IX. DisposabilityStart fast, shut down gracefully on SIGTERM, survive sudden death.Fast scaling, safe rolling deployments and robustness to crashes.Slow-starting runtimes need startup probes or faster start techniques; background jobs must be idempotent and resumable.
X. Dev/prod parityKeep development, staging and production as similar as possible in time, people and tools.Fewer surprises that only appear in production; continuous deployment becomes safe.Full parity is too costly; prioritise the same types and versions of backing services, using containers locally.
XI. LogsWrite logs as an unbuffered event stream to standard output; let the platform route them.The app does not manage files; logs from all copies are collected centrally.Use structured logs plus metrics and traces (for example through OpenTelemetry); watch log volume and cost.
XII. Admin processesRun one-off tasks (migrations, scripts) as separate processes using the same code and config.Admin tasks run against exactly the release in production, not a stale copy.Run them as pipeline steps or Kubernetes Jobs; never let every replica run migrations at start-up without a lock.

Config, secrets and backing services

The config factor has a simple test: could the codebase be made public today without exposing a single credential? If not, configuration has leaked into the code. Separating configuration from code lets one artefact move through every environment unchanged, which is the foundation of the build-once principle in Section 10.5 (CI/CD and deployment). Configuration includes database and service URLs, credentials, and per-environment settings; it excludes internal wiring that is the same everywhere, such as which class handles which route, which belongs in the code.

The original recommendation to use environment variables has aged unevenly. They are universal and language-neutral, and Kubernetes, PaaS platforms and functions all support them. But a process's environment is visible to child processes, is often dumped in crash reports and debugging pages, and cannot be rotated without a restart. Current practice for secrets is to fetch them at start-up from a secrets manager (AWS Secrets Manager, Azure Key Vault, Google Secret Manager, HashiCorp Vault or OpenBao) or mount them as files that the platform can update, and better still to replace long-lived secrets with workload identity, where the platform gives each service short-lived credentials automatically. The factor's intent, keeping configuration outside the artefact, is unchanged.

Treating a backing service as an attached resource means that the code reaches the orders database, the payment provider and the message broker the same way: through a URL and credentials supplied at deploy time. That makes environments composable and recovery quicker. It does not make backing services interchangeable by magic; replacing one message broker with another changes delivery semantics. The hexagonal style of Chapter 5 complements the factor: configuration says where the resource is, and an adapter behind a port hides how it behaves.

Build, release, run

The build, release, run factor separates three stages that are often blurred. The build stage turns a commit into an executable artefact, today usually a container image identified by its digest. The release stage combines that artefact with the configuration of one environment and gives the result an identity, such as release v42 of the orders service in production. The run stage starts processes from a release. Releases are immutable: a change of code or configuration produces a new release, never an edit to an existing one.

Build once, release per environment, run anywhere

Build, release, run: a commit is built once into an image, combined with an environment's configuration into an immutable numbered release, and run by the platform; rollback runs an earlier releaseCodebasecommit a1b2c3Buildimage @sha256:9f...Production configURLs, secrets refsRelease v42image + configRunprocesses in proddevelopers change codeCI builds it onceimmutable, numberedplatform starts itRelease historyv40v41v42rollback: run v41 again, no rebuild
Because releases are immutable and numbered, rolling back means running an earlier release again, not rebuilding or editing anything.

This separation is what makes deployment boring. Nobody edits files on a server, so servers do not drift; the same image that passed testing in staging runs in production, so a test result means something; and rollback is a fast, rehearsed operation instead of an emergency rebuild. It is also an audit trail: for any moment you can say exactly which code and which configuration were running. Kubernetes keeps a revision history of each Deployment's ReplicaSets, PaaS platforms number releases, and GitOps (Section 10.4) makes the Git history of the release repository the record.

Processes, concurrency and disposability

Stateless, share-nothing processes are the precondition for almost everything else in this chapter: horizontal scaling, rolling deployments, autoscaling, replacing failed nodes and spreading across zones. Anything a process keeps only in its memory or on its local disk (a user's session, an uploaded file, a partly built report) is lost when that process is replaced, and is invisible to the other copies. Chapter 9 discusses session handling in detail (load balancing, statelessness and sessions). State does not disappear; it moves to backing services designed to keep it.

The concurrency factor adds that different kinds of work should run as different process types that scale separately. In the food-delivery platform, the web process serving the customer app scales with request rate, while a worker process sending order confirmations scales with queue length and a courier-location consumer with event throughput. Mixing them in one process type means scaling all of them for the needs of one.

Disposability asks that processes start in seconds and stop cleanly. On SIGTERM a web process stops accepting connections and finishes requests in flight; a worker returns its current message to the queue or finishes it within the grace period. Because processes can also die without warning (a node failure, an out-of-memory kill), work must be safe to repeat: idempotent message handling, as in event-driven architecture, and jobs that resume from checkpoints. George Candea and Armando Fox pushed the idea to its limit in 2003 with crash-only software, which has only one way to stop (crashing) and one way to start (recovery), so that the recovery path is exercised constantly rather than discovered during an incident.

Dev/prod parity, logs and admin processes

The original text identifies three gaps between development and production: time (code written weeks before it is deployed), personnel (developers write it, operators deploy it) and tools (SQLite on a laptop, PostgreSQL in production). Continuous delivery closes the first, teams that run what they build close the second, and containers close much of the third: a developer can run the same PostgreSQL version, message broker and cache locally, and tools such as Testcontainers start real dependencies in integration tests. Perfect parity is neither affordable nor necessary; what matters most is using the same kinds and versions of backing services, because that is where the surprising differences live.

Logs as event streams means that the application writes each event to standard output and leaves storage, rotation and shipping to the platform. That remains right, with two modern additions: logs should be structured (for example JSON with consistent field names and a request or trace identifier), and logs are one of three signals, alongside metrics and traces, that make a system observable (observability). Log volume is also a real cost driver; Section 10.8 returns to it.

Admin processes, such as database migrations, data repairs and one-off reports, should run with the same release (code and configuration) as the application, as separate processes. In Kubernetes that typically means a Job run by the pipeline before the new version rolls out. Migrations need particular care: they must be compatible with both the old and the new version of the code running side by side, which is the expand-and-contract discipline of schema evolution.

Beyond twelve factors

The twelve factors were never meant to be complete, and later authors extended them. Kevin Hoffman's Beyond the Twelve-Factor App (O'Reilly, 2016) revised the list to fifteen, adding API first (design the contract before the implementation), telemetry (build in metrics, health and tracing) and authentication and authorisation (security as a design concern from the start). Current practice adds four themes that every cloud-native application should meet.

Disposability, taken seriously. Design for being killed at any moment: idempotent operations, short request timeouts, bounded start-up time, graceful drain, and no single process whose loss matters. Immutability. Never patch a running container or server; build a new image and replace it (Section 10.4 on immutable infrastructure). This makes every running instance traceable to a commit and removes drift. Observability. Emit structured logs, metrics and traces through a standard such as OpenTelemetry, expose health endpoints that reflect the process's own state, and propagate trace context across calls, so that operators can answer questions they did not anticipate. Security. Run as a non-root user with a read-only file system where possible; obtain credentials through workload identity rather than long-lived keys; apply least privilege to every backing service; and build from minimal, scanned, signed images (supply-chain security).

Worked example

Worked example: auditing the restaurant-onboarding service

The food-delivery platform's restaurant-onboarding service is a five-year-old web application on two hand-built virtual machines behind a load balancer with sticky sessions. The team wants to move it to the company's container platform. An audit against the factors finds nine problems; each is given a risk, a fix and a priority.

1. Production database password in a properties file in the repository (III)Risk: anyone with read access to the code, including former contractors, has the credential. Fix: rotate the password now, move it to the secrets manager, inject at deploy time. Priority 1.
2. Uploaded menus and logos written to the VM's local disk (VI, IV)Risk: files vanish when a request lands on the other VM or a VM is replaced; blocks any move to disposable containers. Fix: object storage, with a one-off copy of existing files. Priority 1.
3. Sessions in process memory, sticky load balancing (VI)Risk: every deployment logs restaurant staff out mid-form. Fix: signed session tokens or a shared session store. Priority 2.
4. Deployed by SSH, git pull and restart on each VM (V, II)Risk: the two VMs run different code after a failed pull; no rollback. Fix: CI builds one image per commit; numbered releases. Priority 2.
5. Migrations run automatically at start-up by both instances (XII)Risk: two instances race on the same migration. Fix: a separate migration step in the pipeline, with expand-and-contract changes. Priority 2 (Priority 1 if a race has already happened).
6. Killed with kill -9 during deploys (IX)Risk: half-saved onboarding forms. Fix: handle SIGTERM, drain requests, add readiness and a pre-stop delay. Priority 2.
7. Logs written to rotating files on each VM (XI)Risk: logs lost with the VM; hard to search across instances. Fix: structured JSON to standard output, collected centrally. Priority 3, cheap to do alongside item 4.
8. Nightly payout report as a cron entry on VM 1 only (XII, VIII)Risk: the job silently stops when VM 1 is rebuilt, or runs twice if copied. Fix: a scheduled job on the platform, made idempotent. Priority 3.
9. Developers use SQLite, production uses PostgreSQL (X)Risk: SQL that works locally fails in production. Fix: PostgreSQL in a container for development and tests. Priority 3.

Priorities follow risk first (an exposed credential is a live incident), then whatever blocks the target platform (local state), then reliability, then convenience.

The team does not need to fix everything before migrating: items 1 and 2 are prerequisites; items 3 to 6 can be done during the move; items 7 to 9 can follow.

An audit like this is also good material for an architecture decision record: it explains why each change is being made, in what order, and what was deliberately deferred.

Questions people ask

Does every application need to satisfy all twelve factors?

No. They are a checklist of properties that make deployment, scaling and recovery cheap on a modern platform. Treat each gap as a risk with a cost, and fix those that block your platform or threaten security first, as in the audit above.

Does the methodology apply to serverless functions?

Largely, and the platform enforces most of it: functions are stateless, configured from outside, disposable and logged by the platform. The factors that still need attention are config and secrets, backing services, idempotency and dev/prod parity, since local emulation of a provider's event sources is imperfect.

Is twelve-factor still relevant now that Kubernetes exists?

Yes, because Kubernetes assumes it. A pod that keeps sessions in memory, writes files locally or takes two minutes to start fights the platform's rolling updates, autoscaling and rescheduling. The factors describe what an application must do so that the platform's automation helps rather than hurts.

Check your understanding

What simple test shows whether configuration has leaked into the code?

Ask whether the codebase could be made public today without exposing any credential. If not, configuration (at least the secrets) lives in the code.

Why does build, release, run make rollback fast?

Each release is an immutable combination of a built artefact and configuration with its own identity, so rolling back means running the previous release again, without rebuilding or editing anything.

A service stores uploaded files on the local disk of its server. Which factor does this violate and what breaks?

Processes (VI), and arguably backing services (IV): files are missing when requests reach another copy, and are lost when the instance is replaced, which blocks scaling out and disposable containers.

Why should migrations not run automatically in every replica at start-up?

Several replicas may run the same migration at once and race, and start-up becomes slow and fragile. Migrations should run once, as a separate admin step in the pipeline, using the same release.

Next · 10.4Infrastructure as codeDeclarative and imperative infrastructure code; Terraform, OpenTofu, CloudFormation, Bicep and Pulumi; state, locking, drift, modules and environments; immutable infrastructure; GitOps; policy as code; and how to test it all.Continue →

Sources and further reading

Have a question about this section? ArchitectBot, the course’s AI study assistant, answers from the course first.

Ask: “Which twelve-factor gaps should we fix before moving an old service onto Kubernetes, and which can wait?”