NodeJSThe Twelve-Factor App

The Twelve-Factor App

The Twelve-Factor App is a methodology for building modern, deployable software-as-a-service applications — twelve principles derived from observing what makes apps portable, scalable, maintainable, and straightforward to deploy. Published by Heroku's founders, it predates containers but describes exactly what makes containerized, cloud-native apps work. Each factor is a concrete practice, and Node apps that follow them are easier to deploy, easier to scale, and less prone to environment-specific failures. This page summarizes all twelve and shows how each applies to Node.

The twelve factors — at a glance

Factor

The rule

I

Codebase

One repo, many deploys — never one repo per env

II

Dependencies

Declare all deps in package.json; never rely on system packages

III

Config

Store config in the environment (not the code)

IV

Backing services

Treat DB, Redis, S3 as attached resources — swappable via URL

V

Build, Release, Run

Strict separation: build once, release = build + config, run is stateless

VI

Processes

Execute as stateless, share-nothing processes

VII

Port binding

Export services by binding to a port (app.listen)

VIII

Concurrency

Scale out via the process model (cluster, replicas), not threads

IX

Disposability

Fast startup, graceful shutdown; handle SIGTERM

X

Dev/prod parity

Keep dev, staging, prod as similar as possible

XI

Logs

Treat logs as event streams — write to stdout, never manage files

XII

Admin processes

Run admin tasks (migrations) as one-off processes in the same environment

The factors most relevant to Node
Factor III (Config) and Factor XI (Logs) are the two most commonly violated in Node apps — both have concrete, easy fixes
Most Twelve-Factor violations cluster around two factors. **Factor III (Config)**: credentials and environment-specific values hard-coded in source, or stored in files that are committed — fix with environment variables and a [validated config module](/nodejs/config-best-practices). **Factor XI (Logs)**: writing to log files that the app manages itself (`fs.createWriteStream('app.log')`) rather than streaming to stdout — fix by using [structured logging](/nodejs/structured-logging) libraries (`pino`, `winston`) that write to stdout and letting the platform (Docker, systemd, Kubernetes) capture and route the stream. The platform can ship stdout to any log aggregator without the app knowing; an app managing its own log files couples itself to the deployment environment.
Factor VI — stateless processes

TS
// ❌ State stored in process memory — lost on restart, invisible to other instances:
const sessionCache = new Map<string, Session>()   // wiped on every deploy

// ✅ State in an external backing service — survives restarts, shared across instances:
// Store sessions in Redis:
await redis.set(`session:${id}`, JSON.stringify(session), { EX: 3600 })
const session = JSON.parse(await redis.get(`session:${id}`) ?? 'null')
Stateless processes are the prerequisite for horizontal scaling — any in-process state is lost on restart and invisible to other replicas
**Factor VI** is the prerequisite for everything else in the horizontal scaling story. A "share-nothing" process keeps **no state between requests** that isn't stored in a [backing service](/nodejs/redis) (database, Redis, message queue). No in-memory caches that persist across requests, no session data in process-local variables, no per-instance counters. The reasons are concrete: process memory is lost on *every restart* (every deploy wipes it), and in a [clustered or replicated setup](/nodejs/pm2), the next request may land on a *different instance* that never saw the state you wrote. Move everything that needs to survive restarts or be shared across instances — sessions, job state, rate-limit counters, WebSocket room memberships — into an external store. Once stateless, you can add or remove instances freely, roll out new versions without sticky routing, and restart processes without data loss.
Factor IX — disposability

JS
// FAST STARTUP: minimize module-level work — defer DB connections to first use
//   or warm them early but don't block startup on slow external calls.

// GRACEFUL SHUTDOWN: respond to SIGTERM without dropping in-flight requests:
process.on('SIGTERM', async () => {
  server.close(async () => {          // stop accepting, finish in-flight
    await db.end()                    // release resources
    process.exit(0)
  })
  setTimeout(() => process.exit(1), 10_000).unref()  // hard cap
})
Disposable processes start fast and shut down gracefully — the combination enables zero-downtime deploys and fast crash recovery
**Factor IX** means processes are **disposable**: they start quickly and shut down cleanly. **Fast startup** matters because cloud platforms and orchestrators spin up instances to absorb traffic — a 30-second boot time defeats auto-scaling. Keep module-level initialization minimal; defer or parallelize slow startup work (pre-warming DB pools). **Graceful shutdown** (handling `SIGTERM` as covered in [preparing for production](/nodejs/preparing-for-production)) means deploys and scale-downs don't drop live requests. Together these two properties enable **zero-downtime rolling deploys**: the orchestrator starts the new version, waits for its health check, routes traffic to it, then signals the old version with `SIGTERM` and waits for graceful exit. If startup is slow or shutdown drops requests, rolling deploys cause visible errors.
Next
One of the most powerful structural techniques for testability: [Dependency Injection](/nodejs/dependency-injection).