The Twelve-Factor App
Status: Complete
Category: Architecture
Default enforcement: Advisory
Author: PushBackLog team
Tags
- Topic: architecture, quality
- Methodology: Cloud-native
- Skillset: backend, devops
- Technology: generic
- Stage: execution, deployment
Summary
The Twelve-Factor App is a methodology for building software-as-a-service applications that are portable, deployable to modern cloud platforms, and designed for continuous delivery. Its twelve factors address codebase management, dependencies, configuration, backing services, builds, processes, port binding, concurrency, disposability, parity, logging, and admin processes.
Rationale
Written for the cloud era
The Twelve-Factor methodology was documented by Adam Wiggins of Heroku in 2011, distilled from deployment patterns across thousands of applications. It describes what makes an application reliably deployable to cloud infrastructure, scalable horizontally, and maintainable over time. Fifteen years later it remains the baseline contract between an application and its platform.
Teams that follow the twelve factors produce applications that:
- can be deployed to any cloud provider without code changes
- scale horizontally by adding instances, not by enlarging servers
- are operationally transparent — the platform can observe, restart, and route them without application-bespoke knowledge
- can be developed in parity with production, reducing environment-specific defects
Each factor addresses a specific failure mode
The factors aren’t arbitrary rules. Each one addresses a specific class of operational or architectural problem observed in pre-cloud, “traditional” application deployment:
- Locked to a specific OS or server → Factor I (Codebase), III (Config)
- Works on developer laptop, fails in production → Factor X (Dev/prod parity)
- Cannot scale without sticky sessions → Factor VI (Stateless processes)
- Debugging requires SSH into production → Factor XI (Logs)
Guidance
The twelve factors
| # | Factor | What it means |
|---|---|---|
| I | Codebase | One codebase tracked in version control; many deploys (dev, staging, prod) from the same codebase. Not one repo per environment. |
| II | Dependencies | Explicitly declare all dependencies (package.json, requirements.txt, go.mod). Never assume system-level tools exist. Use dependency isolation (virtualenv, node_modules). |
| III | Config | Everything that varies between deploys (database URLs, API keys, feature flags) is stored in environment variables, not in code or committed files. |
| IV | Backing services | Databases, queues, caches, email services are attached resources — accessed by URL in config. Swap a local Postgres for an RDS instance by changing an env var; no code changes. |
| V | Build, release, run | Strictly separate: build (compile, bundle) → release (build + config) → run (execute). Never modify code in a running instance. Releases are immutable; roll back by deploying a previous release. |
| VI | Processes | Execute the app as stateless processes. No sticky sessions, no local file state between requests. State lives in backing services (database, cache, queue). |
| VII | Port binding | The app exports its service by binding to a port. It doesn’t require a web server embedded in the runtime; the app IS the web server. Enables app-to-app communication as backing services. |
| VIII | Concurrency | Scale out by adding more processes, not by making the process larger. Use the OS process model; use worker types (web, worker, clock) to scale each concern independently. |
| IX | Disposability | Processes can start fast and stop gracefully. Fast startup enables elastic scaling. Graceful shutdown (handle SIGTERM, finish in-flight requests) prevents data loss. |
| X | Dev/prod parity | Keep development, staging, and production as similar as possible: same backing service versions, same OS, deployed continuously rather than in big batches. |
| XI | Logs | Treat logs as event streams. The app writes to stdout; the platform collects, routes, and stores logs. No log file management in the app. |
| XII | Admin processes | Run management tasks (migrations, one-time scripts) as one-off processes in the same environment as the app. Not cron jobs, not SSH sessions — disposable run commands. |
The most commonly violated factors
In practice, teams most frequently violate:
- III (Config): credentials in code or committed
.envfiles - X (Dev/prod parity): using SQLite locally, PostgreSQL in production
- VI (Processes): storing uploaded files on the local filesystem (lost on restart)
- XI (Logs): writing rotating log files instead of stdout
Examples
Factor III: Config in env vars
# Wrong: config in committed file
# config/database.js
module.exports = { host: 'prod-db.internal', user: 'admin', password: 'secret' };
# Right: read from environment at runtime
const config = {
databaseUrl: process.env.DATABASE_URL, // injected by platform
stripeKey: process.env.STRIPE_SECRET_KEY
};
Factor VI: Stateless processes
// Wrong: in-memory session state
const sessions = new Map<string, Session>(); // Lost on restart; breaks with multiple instances
// Right: sessions in a backing service
const session = await redis.get(`session:${sessionId}`);
Factor IX: Graceful shutdown
process.on('SIGTERM', async () => {
await server.close(); // Stop accepting new requests
await db.end(); // Close DB pool cleanly
process.exit(0);
});
Anti-patterns
1. Sticky sessions
Routing users to specific instances breaks horizontal scale. Factor VI requires that any instance can handle any request. Use a shared session store (Redis, database) instead.
2. Different databases in dev and production
SQLite locally, PostgreSQL in production. Bugs that only appear in production are the direct consequence. Factor X requires the same backing service type and version across environments.
3. Bundling config into images
Baking database URLs or API keys into container images produces images that are environment-specific. The image should be environment-agnostic; config is injected at runtime.
4. Cron jobs that SSH into servers
Factor XII says admin processes should run as disposable processes in the same environment as the app. Arbitrary crons that shell into production are untracked, unversioned, and invisible.
Related practices
Part of the PushBackLog Best Practices Library. Suggest improvements →