The Twelve-Factor App is a set of principles for building web applications that are easy to deploy, scale and maintain. It was written by Heroku engineers in 2011, but it’s still the best short checklist for apps that run in containers or on VPS servers. Here’s each factor in plain language, with what it means in practice.

The twelve factors

1. Codebase: one codebase, many deploys

One repository per app (or one app per directory in a monorepo). Staging and production run the same code, at different versions. See Git branching strategies.

2. Dependencies: declare and isolate them

Every dependency is declared (package.json, requirements.txt, composer.json) with a lockfile, and installed in isolation (virtualenv, node_modules, the container image). No relying on whatever happens to be installed on the server. See Python virtual environments.

3. Config: store it in the environment

Anything that differs between environments (database URL, API keys, feature toggles) comes from environment variables or mounted config, never hard-coded:

DATABASE_URL=postgres://app:secret@db:5432/app
REDIS_URL=redis://redis:6379/0

Test: could you open-source the code right now without leaking credentials? See Docker environment variables and secrets management.

4. Backing services: treat them as attached resources

Databases, caches, queues, object storage and SMTP are resources reached by URL. Swapping a local PostgreSQL for one on another server should be a config change only.

5. Build, release, run: separate them strictly

  • Build: turn code into an artifact (a Docker image).
  • Release: combine the build with config, and give it an ID (version tag).
  • Run: start it.

You never edit code on the server. Rollback means running an older release. See Docker image tags best practices.

6. Processes: stateless and share-nothing

App processes keep no important state in memory or on local disk. Sessions go in Redis or the database, and uploads in object storage. That makes scaling and replacing processes safe. See VPS scaling.

7. Port binding: export services via a port

The app is self-contained, and listens on a port (:8080). A reverse proxy in front handles TLS and routing. See Nginx reverse proxy.

8. Concurrency: scale out via the process model

Different workloads run as different process types (web, worker, scheduler), each scaled independently, for example more Compose replicas, or more systemd instances. See Docker Compose scale.

9. Disposability: fast startup, graceful shutdown

Processes start in seconds, and handle SIGTERM by finishing in-flight requests and jobs. That makes deploys, restarts and autoscaling smooth. See Linux signals.

10. Dev/prod parity: keep environments similar

Same backing service types and versions in development, staging and production (Docker Compose makes this easy). Small, frequent deploys keep the gap small.

11. Logs: treat them as event streams

The app writes logs to stdout/stderr, and the environment collects them (Docker, journald, a log shipper). It doesn’t manage log files itself. See log management.

12. Admin processes: run them as one-off processes

Migrations, console tasks and scripts run in the same environment and release as the app, for example:

docker compose run --rm web python manage.py migrate

Beyond the original twelve

Modern additions people often cite: API first, telemetry (metrics and traces), and security (authentication, secrets handling). See observability basics.

Applying it on a VPS

A typical twelve-factor setup on a single VPS:

  • the app image built in CI, tagged with a version;
  • compose.yaml with web and worker services, and config from .env;
  • PostgreSQL and Redis as services (or managed elsewhere), reached by URL;
  • Nginx or Caddy in front, terminating TLS;
  • logs collected from Docker into Loki;
  • migrations as a one-off docker compose run step in the deploy.

See Docker Compose in production.

Quick reference

Factor In practice
Config Environment variables / .env
Build/release/run CI builds images; deploy by tag
Processes Stateless; state in DB/Redis/S3
Disposability Handle SIGTERM gracefully
Logs stdout → collector
Admin tasks One-off containers/commands

Related: DevOps for small teams and deployment strategies.

  • DevOps
  • Architecture
  • Best Practices