Skip to main content
The 12 Factor App: Principles That Actually Matter

The 12 Factor App: Principles That Actually Matter

Jul 5, 2025

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

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.

© 2026 Tawan. All rights reserved.