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"]
| Do | Don’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 Type | Count | Description |
|---|---|---|
| Web | 4 | Handle HTTP requests |
| Worker | 2 | Background jobs |
| Clock | 1 | Scheduled 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.
| Gap | Traditional | Twelve-Factor |
|---|---|---|
| Time | Weeks to months | Hours to days |
| Personnel | Developers and ops are different | Same person deploys |
| Tools | SQLite in dev, PostgreSQL in prod | Use 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