The Twelve-Factor methodology is from 2011 but remains relevant. Here’s what matters in practice, and what’s become outdated.

The Factors, Ranked by Impact

Critical (Ignore at Your Peril)

III. Config in Environment

1
2
3
4
5
# Bad
DATABASE_URL = "postgres://localhost/myapp"  # hardcoded

# Good
DATABASE_URL = os.environ["DATABASE_URL"]

Config includes credentials, per-environment values, and feature flags. Environment variables work everywhere: containers, serverless, bare metal.

VI. Stateless Processes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Bad: storing session in memory
sessions = {}

@app.post("/login")
def login(user):
    sessions[user.id] = {"logged_in": True}  # Dies with process

# Good: external session store
@app.post("/login")  
def login(user):
    redis.set(f"session:{user.id}", {"logged_in": True})

If your process dies, can another pick up the work? Statelessness enables horizontal scaling, rolling deploys, and crash recovery.

X. Dev/Prod Parity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# docker-compose.yml for local dev
services:
  db:
    image: postgres:15  # Same version as prod
  redis:
    image: redis:7      # Same version as prod
  app:
    build: .
    environment:
      - DATABASE_URL=postgres://db/app

“Works on my machine” usually means dev differs from prod. Docker Compose eliminates most of this.

Important (Worth Following)

IV. Backing Services as Attached Resources

1
2
3
4
# Database, cache, queue, email - all just URLs
DATABASE_URL = "postgres://user:pass@host:5432/db"
REDIS_URL = "redis://host:6379/0"
SMTP_URL = "smtp://user:pass@smtp.example.com:587"

Your app shouldn’t care if Postgres is local or RDS. Same code, different config.

IX. Disposability

  • Fast startup (seconds, not minutes)
  • Graceful shutdown (finish requests, close connections)
  • Crash-safe (can restart without corruption)

This enables auto-scaling and smooth deploys.

XI. Logs as Streams

1
2
3
4
5
# Bad: writing to files
logging.FileHandler("/var/log/myapp.log")

# Good: writing to stdout
logging.StreamHandler(sys.stdout)

Let the platform handle log aggregation. Your app just writes to stdout.

Nice to Have (Context-Dependent)

I. One Codebase, Many Deploys

Monorepo vs polyrepo is a religious war. The real point: don’t copy-paste code between environments.

II. Explicit Dependencies

#fuavrsietcqaoupriinr==e==m00e..n12t07s9...0t0xt/package.json/go.mod

Pin versions. Use lockfiles. This is table stakes in 2026.

V. Build, Release, Run

BRRueuilnle:da:se:didomecapkgleeor:yabbrucei1ll2ed3as+e:icvmo1an.gf2ei.:g3abc1r2e3lease:v1.2.3

Immutable artifacts. Never modify running code.

VII. Port Binding

1
2
# App is self-contained, exports HTTP on a port
uvicorn app:app --host 0.0.0.0 --port $PORT

No Apache/nginx required inside your app. Reverse proxy sits in front.

VIII. Concurrency via Process Model

wwceolbro=kc3ekr==12###321wbseacbchkepgdrruoolcueenrsdsewsorkers

Scale by adding processes, not threads. Kubernetes/ECS does this naturally.

XII. Admin Processes

1
2
3
4
# Run migrations as one-off process
kubectl run migrate --image=myapp -- python manage.py migrate

# Not: ssh into server and run commands

Admin tasks use the same codebase and config as the app.

What’s Outdated

“One codebase tracked in revision control” - Monorepos and polyrepos both work. The factor was really about “don’t have slightly different code in prod vs staging.”

“Explicitly declare and isolate dependencies” - Everyone uses package managers now. This was revolutionary in 2011.

“Port binding” - With containers, this is automatic. You don’t think about it.

The Modern Interpretation

If I were rewriting twelve-factor today:

  1. Config in environment (still critical)
  2. Stateless processes (still critical)
  3. Logs to stdout (still critical)
  4. Graceful shutdown (added emphasis)
  5. Health checks (new factor)
  6. Immutable deploys (combines build/release/run)
  7. Backing services as URLs (still relevant)
  8. Dev/prod parity via containers (updated)
  9. Horizontal scaling (clarified)
  10. Observability (new: metrics, traces, logs)

Quick Compliance Check

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Can you change database without code change?
echo $DATABASE_URL  # ✓ if this works

# Can you run two instances?
docker run -d myapp
docker run -d myapp  # ✓ if both work

# Can you see logs?
docker logs myapp  # ✓ if output appears

# Can you restart without data loss?
docker restart myapp  # ✓ if app recovers

Twelve-factor isn’t a checklist to follow blindly. It’s a set of principles that make apps easier to deploy, scale, and operate. Use what helps, skip what doesn’t.