E
Emin Muhammadi
November 20256 min read

The Twelve-Factor App: Principles for Cloud-Native Services

The Twelve-Factor App is a methodology for building modern software-as-a-service applications that are easy to deploy, scale, and maintain. It was developed by engineers at Heroku and encapsulates twelve best practices covering code management, confi...

The Twelve-Factor App: Principles for Cloud-Native Services

The Twelve-Factor App is a methodology for building modern software-as-a-service applications that are easy to deploy, scale, and maintain. It was developed by engineers at Heroku and encapsulates twelve best practices covering code management, configuration, deployment, and operations. These principles address common cloud-era challenges – from ensuring consistent environments to making apps portable and resilient.

Codebase

At the heart of the first factor is the rule that an app should have exactly one codebase, tracked in version control, with many deploys. In other words, your entire application lives in a single Git repository. Each running instance of the app – whether a developer’s local copy, a staging server, or production – is a deploy of that same code. There should never be multiple, divergent codebases for one service. For example, a startup team might host their service in one GitHub repository and use branches or tags to deploy the same code to different environments. This ensures that what runs in production is traceable to the same source code under version control. This one-to-many relationship avoids the confusion of “works on my machine” by making every environment use the identical code (perhaps at different versions).

Dependencies

A Twelve-Factor app explicitly declares and isolates all its dependencies. It does not assume that any library or system package exists by default. Instead, the app uses a manifest (such as requirements.txt or package.json) to list all libraries it needs, and uses tooling (virtual environments, containers, bundlers) to isolate them. For example, a Node.js application will list modules in package.json and install them fresh in each environment using npm ci. Similarly, a Python team might use pip install -r requirements.txt inside a virtualenv or Docker image. Explicit dependency management means a new developer can clone the repo and run one command to get everything installed, and the same setup works in production. In practice, a company might use Docker to containerize its app: the Dockerfile serves as the dependency declaration and build context, so that each release includes only what it needs.

Config

Configuration refers to all the settings that vary between deploys, such as database URLs, API keys, and other credentials. The Twelve-Factor principle is to keep config out of the code and store it in the environment. In practice, this means using environment variables (or other external config stores) rather than hard-coding any environment-specific values. For example, a team might set DATABASE_URL and REDIS_URL in each environment: local development, staging, and production each have their own values. This way the same codebase can be open-sourced without leaking secrets, and the app can easily connect to different resources by changing env vars.

Backing Services

Backing services are any networked services the app uses, such as databases, message queues, or caching systems. The Twelve-Factor rule is to treat backing services as attached resources. Whether a database is local or third-party should not matter to the app: it simply connects to whatever URL is given. For example, an application might be developed using a local MySQL database during development, but in production switch to Amazon RDS by changing the DATABASE_URL in the environment. No code changes are needed – only the resource handle in the configuration changes. This loose coupling means a team can replace or upgrade backing services on the fly.

Diagram showing a "Cloud Native System" in the center, connected by dashed arrows to various "Backing Services": Monitoring, Security Services, Analytics, Distributed Caching, Message Brokers, Relational Databases, Document Databases, Streaming Services, and Storage Services.
Diagram showing a "Cloud Native System" in the center, connected by dashed arrows to various "Backing Services": Monitoring, Security Services, Analytics, Distributed Caching, Message Brokers, Relational Databases, Document Databases, Streaming Services, and Storage Services.

Build, Release, Run

Deployment is split into three distinct stages: build, release, and run. The build stage converts the codebase into an executable bundle (for example, building a Docker image or compiling assets). The release stage takes that build and combines it with the configuration for a given deploy, producing a specific release. Finally, the run stage actually launches the app using that release. For example, a CI/CD pipeline might trigger a build job that produces a Docker image, inject configuration variables, and deploy containers on a Kubernetes cluster. The key is that these stages never intermingle: once in the run stage, the app should be immutable and cannot mutate its own build. This separation makes rollbacks and debugging straightforward.

Processes

A Twelve-Factor app is executed as one or more stateless processes. Each process handles a slice of work, such as one process type handling web requests and another handling background jobs. Critically, processes are share-nothing and do not rely on local memory or filesystem state. Any data that needs to persist must go into a backing service (like a database or cache). For example, a web service might store session data in Redis instead of server memory so that multiple instances can serve user requests interchangeably. This statelessness enables horizontal scaling and resilience.

Port Binding

The app is self-contained and exports services by binding to a port. Instead of relying on an external web server or application container, the app includes its own HTTP server and listens on a specified port. For example, a Node.js Express app might call app.listen(process.env.PORT) to accept HTTP requests directly, rather than running inside Apache or Tomcat. This makes each app an independent service that can be composed into larger systems via simple URLs or ports.

Concurrency

Twelve-Factor apps scale by decomposing into process types and running more of each as needed. Each distinct kind of work is handled by a “process type” – for example, web for HTTP requests and worker for background jobs. The app can run multiple instances of each process type. For example, if traffic grows, a team might start three web processes and two worker processes across several containers. This horizontal scaling of identical, share-nothing processes makes scaling straightforward.

Disposability

Processes should be robust and disposable, with fast startup and graceful shutdown. In practice, this means designing your app so it can be started or stopped quickly in response to scaling or deployment events. Ideally, a process should reach readiness within seconds, and on shutdown, it should finish in-flight requests or jobs before exiting cleanly. This design enables autoscaling, rolling deployments, and resilient systems that can recover gracefully from failures.

Dev/Prod Parity

Keeping development, staging, and production as similar as possible is crucial for continuous delivery. The goal is to minimize the gaps in time, personnel, and tools. For example, a team might use Docker so developers run the same OS, database version, and language runtime locally as in production. They avoid using different stacks across environments because even small differences can cause production-only bugs. By maintaining parity, developers can ship changes more frequently and with fewer surprises.

Logs

Treat logs as event streams, not as files to manage. Twelve-Factor apps do not write logs to disk or attempt to rotate them; instead, each process writes its output (stdout/stderr) to the console. In production, the execution environment captures and aggregates all streams from all processes and routes them to log management systems such as Elasticsearch, Splunk, or cloud log services. This approach keeps applications simple and observability centralized.

Admin Processes

Any one-off administrative tasks, such as database migrations or scripts, should be executed in an identical environment to the app’s regular processes. This ensures consistency and avoids “works on my machine” issues. For example, in Kubernetes, an operator might run a database migration using the same container image used by the application itself. By treating admin processes as first-class citizens, maintenance and ad-hoc operations happen under the same rules as normal runs, keeping the system predictable.

Source: https://12factor.net/

Related Articles