quyennv.com

Senior DevOps Engineer · Healthcare, Singapore

The Twelve-Factor App: Methodology for Software-as-a-Service

#12-factor#cloud-native#devops#saas#continuous-deployment#best-practices

The Twelve-Factor App is a methodology for building software-as-a-service (SaaS) applications—web apps or services delivered over the network. It was created by contributors with experience from the Heroku platform. The factors aim for apps that use declarative setup, have a clean contract with the OS for portability, suit modern cloud platforms, minimize divergence between dev and production for continuous deployment, and scale up without major changes to tooling or architecture. The methodology applies to apps in any language and with any backing services (database, queue, cache, etc.). This post summarizes the twelve factors.


Who it’s for

Any developer building applications which run as a service. Ops engineers who deploy or manage such applications.


The twelve factors (overview)

#FactorSummary
ICodebaseOne codebase in revision control, many deploys.
IIDependenciesExplicitly declare and isolate dependencies.
IIIConfigStore config in the environment.
IVBacking servicesTreat backing services as attached resources.
VBuild, release, runStrictly separate build and run stages.
VIProcessesExecute the app as one or more stateless processes.
VIIPort bindingExport services via port binding.
VIIIConcurrencyScale out via the process model.
IXDisposabilityFast startup and graceful shutdown.
XDev/prod parityKeep development, staging, and production as similar as possible.
XILogsTreat logs as event streams.
XIIAdmin processesRun admin/management tasks as one-off processes.

I. Codebase — One codebase tracked in revision control, many deploys

  • The app is tracked in a version control system (e.g. Git, Mercurial, Subversion). One codebase = one repo (or, in Git, a set of repos sharing a root commit).
  • One-to-one: one codebase per app. Multiple apps sharing the same code is a violation; shared code should be factored into libraries and included via the dependency manager. Multiple codebases form a distributed system; each component is an app and can comply with twelve-factor on its own.
  • Many deploys: one codebase, but many deploys (running instances)—e.g. production, staging, each developer’s local environment. Different versions may be active in each deploy (e.g. commits on staging not yet in production), but they all share the same codebase.

II. Dependencies — Explicitly declare and isolate dependencies

  • Declare all dependencies completely and exactly in a dependency declaration manifest (e.g. Gemfile, requirements.txt, package.json). No reliance on implicit, system-wide packages.
  • Isolate at execution time so that no implicit dependencies “leak in” from the surrounding system (e.g. Bundler + bundle exec, Pip + Virtualenv). Declaration and isolation are used together.
  • The same dependency specification applies to both production and development. New developers need only the language runtime and dependency manager; a single deterministic command (e.g. bundle install, npm ci) sets up the app.
  • Do not rely on implicit presence of system tools (e.g. ImageMagick, curl). If the app needs a tool, vendor it into the app so behavior is consistent across environments.

III. Config — Store config in the environment

  • Config = everything that varies between deploys: canonical hostname, credentials to external services, resource handles to databases and other backing services. Strict separation of config from code; config varies across deploys, code does not.
  • A good test: the codebase could be made open source at any moment without exposing credentials.
  • Internal application config that does not vary between deploys (e.g. routes, wiring of modules) stays in code. Config files not in revision control (e.g. database.yml) are better than constants in code but are easy to accidentally commit and tend to be scattered and language-specific.
  • Twelve-factor: store config in environment variables. They are easy to change per deploy, unlikely to be committed, and a language- and OS-agnostic standard. Env vars are granular and orthogonal; avoid grouping them into fixed “environments” (e.g. development/test/production) that don’t scale as deploys grow (staging, QA, per-developer, etc.).

IV. Backing services — Treat backing services as attached resources

  • Backing services are services the app consumes over the network: databases, message queues, SMTP, caches, etc. They are attached to a deploy via config (e.g. URLs, credentials in env).
  • Treat all backing services as attached resources: no distinction between “local” and “third-party.” A database can be swapped (e.g. from local MySQL to managed RDS) by changing config only; the app does not care. This keeps the app portable and makes it easy to add or replace backends (e.g. add a cache, switch queue provider).

V. Build, release, run — Strictly separate build and run stages

  • A codebase is turned into a (non-development) deploy in three stages:
    • Build: Transform the repo into an executable build (fetch dependencies, compile binaries and assets). Triggered by developers when deploying code.
    • Release: Combine the build with the deploy’s config. The result is a release (build + config), ready to run. Each release has a unique ID (e.g. timestamp, version) and is immutable; any change creates a new release.
    • Run: Execute the app by running the app’s processes against a selected release in the execution environment.
  • Strict separation: No code changes at runtime; runtime cannot push changes back to the build stage. The run stage should have as few moving parts as possible (e.g. restarts after crash or reboot); the build stage can be more complex because a developer is driving it. Deployment tools should support rollback to a previous release.

VI. Processes — Execute the app as one or more stateless processes

  • The app runs as one or more processes. They can range from a single script to many process types with multiple instances (see Concurrency).
  • Stateless, share-nothing. Any data that must persist goes in a stateful backing service (e.g. database). The memory or filesystem of a process may be used only as a brief, single-transaction cache (e.g. read file → process → write to DB). The app must not assume that in-memory or on-disk state will be available on a future request—another process may handle it, or the process may be restarted or moved.
  • Sticky sessions (session state in process memory, expecting the same client to hit the same process) are a violation. Session state belongs in a store with time-expiration (e.g. Memcached, Redis). Asset compilation belongs in the build stage, not at runtime.

VII. Port binding — Export services via port binding

  • The app exports HTTP (or other) services by binding to a port and listening. It is fully self-contained; the web server (e.g. as a dependency or embedded) is part of the app, not the other way around. The execution environment routes traffic to that port. This keeps the app portable and avoids coupling to a specific container or server.

VIII. Concurrency — Scale out via the process model

  • Scale out by adding more processes (and process types), not by vertically scaling a single process. Different work can be assigned to different process types (e.g. web vs worker). The process model and port binding make horizontal scaling and routing straightforward.

IX. Disposability — Maximize robustness with fast startup and graceful shutdown

  • Processes are disposable: they can be started or stopped at any time. This supports fast scaling, rapid deploys, and robustness.
  • Fast startup: Ideally a process is ready to accept work within seconds. Short startup helps releases and scaling and allows the process manager to relocate processes easily.
  • Graceful shutdown: On SIGTERM, a web process stops listening (refuses new requests), finishes in-flight requests, then exits. Worker processes return the current job to the queue (e.g. NACK in RabbitMQ) so it can be retried; jobs should be reentrant or idempotent. The app should also tolerate sudden death (e.g. hardware failure); a robust queue that returns jobs on disconnect helps.

X. Dev/prod parity — Keep development, staging, and production as similar as possible

  • Minimize gaps between development, staging, and production in time (deploy often), personnel (same people develop and deploy), and tools (same stack and backing services where possible). Use the same dependency and config approach everywhere so that continuous deployment is feasible and bugs show up early.

XI. Logs — Treat logs as event streams

  • Logs are the stream of aggregated, time-ordered events from all running processes and backing services. They have no fixed start or end; they flow while the app runs.
  • The app does not route or store logs. Each process writes its event stream, unbuffered, to stdout. In local dev, the developer watches that stream in the terminal. In staging/production, the execution environment captures, collates, and routes streams to files, log indexers (e.g. Splunk), or data warehouses (e.g. Hadoop/Hive) for alerting, graphing, and search. The app stays agnostic to those destinations.

XII. Admin processes — Run admin/management tasks as one-off processes

  • One-off admin or management tasks (e.g. DB migrate, seed data, console) run as one-off processes in the same environment and release as the app. They use the same codebase and config and are run via the same dependency and env setup, not ad-hoc on a different machine or with different code.

Summary

GoalHow the factors support it
PortabilityClean contract with OS; config in env; backing services as attached resources; port binding; no implicit system deps.
ScalabilityStateless processes; concurrency via process model; disposability; logs as streams.
Continuous deploymentOne codebase, many deploys; build/release/run separation; dev/prod parity; disposability.
OperabilityExplicit dependencies; env-based config; logs to stdout; admin as one-off processes; graceful shutdown.

Reference

The Twelve-Factor App methodology is documented at 12factor.net, including full explanations, rationale, and examples for each factor. The format is inspired by Martin Fowler’s Patterns of Enterprise Application Architecture and Refactoring, and the methodology is widely used as a reference for cloud-native and SaaS applications.

← All posts

Comments