The 12 Factor App methodology came out in 2011. Thats ancient in tech years. But heres the thing - these principles are more relevant now then ever with containers, Kubernetes, and cloud-native everything.
Lets break down which ones actually matter and why.
The 12 Factors at a Glance
The Most Important Ones
Factor 3: Config in Environment
This is the one people mess up most. Hardcoding config is a disaster waiting to happen.
// ❌ Bad - hardcoded
const dbUrl = "postgres://localhost:5432/myapp";
const apiKey = "sk-1234567890";
// ✅ Good - from environment
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;
if (!dbUrl || !apiKey) {
throw new Error("Missing required environment variables");
}
Why it matters: Same code runs in dev, staging, and prod. Only the config changes.
Factor 6: Stateless Processes
Your app should not store anything in memory between requests.
// ❌ Bad - storing state in memory
const sessions = new Map();
app.post('/login', (req, res) => {
sessions.set(req.body.userId, { loggedIn: true });
});
// ✅ Good - external session store
app.post('/login', async (req, res) => {
await redis.set(`session:${req.body.userId}`, JSON.stringify({ loggedIn: true }));
});
Why it matters: You cant scale horizontally if instances need to share memory.
Factor 9: Disposability
Apps should start fast and shutdown gracefully.
// Graceful shutdown
process.on('SIGTERM', async () => {
console.log('Shutting down gracefully...');
// Stop accepting new requests
server.close();
// Finish in-flight requests
await new Promise(resolve => setTimeout(resolve, 5000));
// Close database connections
await db.close();
process.exit(0);
});
Why it matters: Kubernetes kills pods all the time. If your app doesnt handle it gracefully, users see errors.
Factor 11: Logs as Streams
Dont write to log files. Write to stdout and let the platform handle it.
// ❌ Bad - writing to files
const fs = require('fs');
fs.appendFileSync('/var/log/app.log', `${new Date()} - Error occurred\n`);
// ✅ Good - stdout
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level: 'error',
message: 'Error occurred',
requestId: req.id
}));
Why it matters: Docker, Kubernetes, CloudWatch - they all capture stdout. File-based logging breaks in containers.
The Ones People Skip
Factor 5: Build, Release, Run
Keep these stages strictly separate:
- Build: Compile code, install deps, create artifact
- Release: Combine artifact with config
- Run: Execute in environment
Never build in production. Never change code in a running container.
Factor 10: Dev/Prod Parity
Your local environment should mirror production.
# docker-compose.yml - same services as prod
services:
app:
build: .
environment:
- DATABASE_URL=postgres://db:5432/app
- REDIS_URL=redis://redis:6379
db:
image: postgres:15 # Same version as prod!
redis:
image: redis:7 # Same version as prod!
Common mistakes:
- SQLite locally, Postgres in prod
- Different Node versions
- Missing services (Redis, queue, etc.)
Quick Checklist
Before deploying, verify:
- [ ] No hardcoded config (check for localhost, API keys)
- [ ] App is stateless (no in-memory sessions)
- [ ] Graceful shutdown handling
- [ ] Logs go to stdout as JSON
- [ ] Dependencies pinned to specific versions
- [ ] Dev environment mirrors prod
- [ ] App starts in under 30 seconds
Further Reading
- 12factor.net - The original methodology
- Beyond the 12 Factor App - Updated for modern cloud
- Kubernetes Best Practices
The 12 factors arent just theory. Theyre battle-tested patterns that make your app easier to deploy, scale, and maintain. Ignore them and youll learn why the hard way.
