| name | twelve-factor-app |
| description | Apply the Twelve-Factor App methodology to build scalable, maintainable cloud-native applications. Outputs compliance checklist, configuration audit, and refactoring recommendations. |
| argument-hint | ["application type","deployment target","current violations","team maturity"] |
| allowed-tools | Read, Write, Bash |
Twelve-Factor App
The Twelve-Factor App is a methodology for building software-as-a-service apps that are portable, scalable, and maintainable. Violating the factors creates operational complexity, deployment friction, and scaling barriers.
The Twelve Factors
| # | Factor | Core Principle |
|---|
| I | Codebase | One codebase, many deploys |
| II | Dependencies | Explicitly declare and isolate |
| III | Config | Store config in the environment |
| IV | Backing Services | Treat as attached resources |
| V | Build, Release, Run | Strictly separate stages |
| VI | Processes | Execute as stateless processes |
| VII | Port Binding | Export services via port binding |
| VIII | Concurrency | Scale out via process model |
| IX | Disposability | Fast startup, graceful shutdown |
| X | Dev/Prod Parity | Keep environments as similar as possible |
| XI | Logs | Treat as event streams |
| XII | Admin Processes | Run as one-off processes |
Config (Factor III)
DATABASE_URL = "postgresql://prod-db:5432/app"
API_KEY = "sk_live_abc123"
import os
DATABASE_URL = os.environ["DATABASE_URL"]
API_KEY = os.environ["API_KEY"]
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
api_key: str
debug: bool = False
max_workers: int = 4
class Config:
env_file = ".env"
settings = Settings()
Processes (Factor VI) — Stateless
class OrderService:
_pending_orders = {}
def add_order(self, order_id, data):
self._pending_orders[order_id] = data
class OrderService:
def __init__(self, redis_client, db):
self._redis = redis_client
self._db = db
def add_order(self, order_id, data):
self._redis.setex(f"order:{order_id}", 3600, json.dumps(data))
Disposability (Factor IX)
import signal
import sys
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
await db_pool.connect()
await cache.ping()
print("Application started")
yield
print("Shutting down: finishing in-flight requests...")
await db_pool.close()
await cache.close()
print("Shutdown complete")
app = FastAPI(lifespan=lifespan)
def handle_sigterm(*args):
print("SIGTERM received — initiating graceful shutdown")
sys.exit(0)
signal.signal(signal.SIGTERM, handle_sigterm)
Logs (Factor XI) — Treat as Streams
import logging
import sys
import json
logging.basicConfig(filename="/var/log/app.log")
logging.basicConfig(
stream=sys.stdout,
level=logging.INFO,
format="%(message)s",
)
import structlog
logger = structlog.get_logger()
logger.info("order_placed", order_id="ord-123", amount=5999, currency="USD")
Compliance Checklist
git remote -v
cat requirements.txt || cat pyproject.toml
grep -r "hardcoded_password\|api_key = " src/
time docker run app:v1.2.3
docker logs <container_id>
Anti-Patterns to Avoid
| Anti-Pattern | Factor | Fix |
|---|
| Config in code or config files committed to git | III | Environment variables only |
| Session stored in process memory | VI | Redis/DB for session state |
| Writing log files inside container | XI | stdout only; use log aggregator |
| Long container startup (>30s) | IX | Lazy connect; health check with startupProbe |
| Dependency installed at runtime | II | All deps in requirements.txt; baked into image |
10 Rules
- One codebase — multiple deploys use the same code with different config.
- All dependencies declared explicitly — no implicit system packages.
- Config is in environment variables — never in code or committed files.
- Backing services (DB, cache, queue) are attached resources — swappable via URL.
- Build artifacts are immutable — same image deploys to staging and prod.
- Processes are stateless — any instance handles any request.
- The app binds to a port and receives requests — no app server required.
- Scale by adding processes — not by making a single process bigger.
- Fast startup (<5s), graceful SIGTERM handling — essential for Kubernetes.
- Log to stdout only — routing, aggregation, and storage are infrastructure concerns.