Twelve-Factor App - Cloud-Native Design Principles

15 min read | 2025.12.11

What is Twelve-Factor App

Twelve-Factor App is a set of 12 design principles for building modern web applications, proposed by Adam Wiggins, co-founder of Heroku, in 2011. It enables design suitable for SaaS (Software as a Service) running in cloud environments.

Why it matters: In today’s world where containerization, microservices, and CI/CD are commonplace, these principles form the foundation of cloud-native application design.

The 12 Principles

1. Codebase

One codebase tracked in version control, many deploys.

flowchart LR
    Repo["Repository"] --> Dev["Development"]
    Repo --> Staging["Staging"]
    Repo --> Prod["Production"]
✗ Anti-patterns
Different repositories for each environment
Multiple apps in one repository

2. Dependencies

Explicitly declare and isolate dependencies.

// package.json - Explicit dependency declaration
{
  "dependencies": {
    "express": "^4.18.0",
    "pg": "^8.10.0"
  }
}
# Don't rely on system-wide installed packages
✗ Depend on global imagemagick
✓ Install explicitly in Dockerfile

3. Config

Store config in environment variables.

// ✗ Bad example: Hardcoding config in code
const dbHost = 'localhost';

// ✓ Good example: Get from environment variables
const dbHost = process.env.DATABASE_HOST;
# Configure via environment variables
DATABASE_HOST=db.example.com
DATABASE_USER=admin
API_KEY=secret123

4. Backing Services

Treat backing services (DB, cache, queue, etc.) as attached resources.

flowchart LR
    App["Application"] --> PG["PostgreSQL<br/>(local or Amazon RDS)"]
    App --> Redis["Redis<br/>(local or ElastiCache)"]
    App --> S3["S3<br/>(external storage)"]

Migration possible by just switching connection via environment variables

5. Build, Release, Run

Strictly separate build, release, and run stages.

flowchart LR
    Code["Code"] --> Build["Build"]
    Build --> Artifact["Executable<br/>artifact"]
    Config["Config"] --> Release["Release<br/>version"]
    Artifact --> Release
    Release --> Run["Running<br/>process"]
DoDon’t
✓ Deploy new releases✗ Edit code directly in production

6. Processes

Execute the app as one or more stateless processes.

// ✗ Bad example: Storing sessions in memory
const sessions = {};
app.use((req, res, next) => {
  sessions[req.sessionId] = req.user;
});

// ✓ Good example: Use external store
const RedisStore = require('connect-redis');
app.use(session({
  store: new RedisStore({ client: redisClient })
}));

7. Port Binding

Export services via port binding.

// The app itself becomes the web server
const express = require('express');
const app = express();

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

8. Concurrency

Scale out via the process model.

Process TypeCountDescription
Web4Handle HTTP requests
Worker2Background jobs
Clock1Scheduled tasks

Adjust process count based on load

9. Disposability

Maximize robustness with fast startup and graceful shutdown.

// Graceful shutdown
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, shutting down gracefully');

  // Stop accepting new requests
  server.close();

  // Let pending requests complete
  await finishPendingRequests();

  // Close DB connection
  await db.close();

  process.exit(0);
});

10. Dev/Prod Parity

Keep development, staging, and production as similar as possible.

GapTraditionalTwelve-Factor
TimeWeeks to monthsHours to days
PersonnelDevelopers and ops are differentSame person deploys
ToolsSQLite in dev, PostgreSQL in prodUse same services
# docker-compose.yml - Use same DB in development as production
services:
  db:
    image: postgres:15
  redis:
    image: redis:7

11. Logs

Treat logs as event streams.

// ✗ Bad example: Write to file
fs.appendFileSync('/var/log/app.log', message);

// ✓ Good example: Output to stdout
console.log(JSON.stringify({
  timestamp: new Date().toISOString(),
  level: 'info',
  message: 'User logged in',
  userId: '123'
}));
# Logs are collected/aggregated externally
docker logs app | fluentd

12. Admin Processes

Run admin tasks as one-off processes.

# Migration
npx prisma migrate deploy

# Data fix script
node scripts/fix-data.js

# Debugging with REPL
node --inspect app.js

Key Implementation Points

Compatibility with Docker

# Twelve-Factor compliant Dockerfile
FROM node:20-slim

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production  # 2. Dependencies

COPY . .

# 7. Port Binding
EXPOSE 3000

# 6. Stateless process
CMD ["node", "server.js"]

Combination with Kubernetes

# Manage config with ConfigMap (3. Config)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  NODE_ENV: production

# Scale with Deployment (8. Concurrency)
apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 3

Summary

The Twelve-Factor App principles are foundational concepts for cloud-native application development. By following these principles, you can build scalable, maintainable, and portable applications. You don’t need to apply everything at once; we recommend gradually adopting them based on your project’s situation.

← Back to list