Skip to content

Latest commit

 

History

History
348 lines (271 loc) · 7.78 KB

File metadata and controls

348 lines (271 loc) · 7.78 KB

12-Factor App Compliance

Django Keel strictly adheres to the 12-Factor App methodology. This document details how each factor is implemented.

I. Codebase

One codebase tracked in revision control, many deploys

Implementation:

  • Single Git repository
  • Multiple deployment environments (dev, staging, production) from same codebase
  • Environment-specific configuration via environment variables

II. Dependencies

Explicitly declare and isolate dependencies

Implementation:

  • All dependencies declared in pyproject.toml
  • Version pinning with >= for security updates
  • Isolated virtual environments (uv/poetry)
  • No system-wide packages assumed
  • Lock files (uv.lock or poetry.lock) for reproducibility

Example:

dependencies = [
    "django>=5.2,<7.0",
    "psycopg[binary]>=3.2.0",
    "gunicorn>=22.0.0",
]

III. Config

Store config in the environment

Implementation:

  • All configuration via environment variables
  • No secrets in code
  • django-environ for structured config
  • .env.example for documentation
  • Different configs per environment without code changes

Example:

import environ

env = environ.Env()
SECRET_KEY = env("DJANGO_SECRET_KEY")
DATABASE_URL = env.db("DATABASE_URL")
DEBUG = env.bool("DEBUG", default=False)

IV. Backing Services

Treat backing services as attached resources

Implementation:

  • All backing services accessed via URLs
  • Database: DATABASE_URL
  • Redis: REDIS_URL
  • Celery: CELERY_BROKER_URL
  • Object Storage: AWS/GCS/Azure URLs
  • Services swappable without code changes
  • Local and remote services treated identically

Example:

DATABASE_URL=postgres://localhost/mydb        # Local
DATABASE_URL=postgres://prod.db.com/mydb      # Remote
# Same connection code, different environment

V. Build, Release, Run

Strictly separate build and run stages

Implementation:

Build Stage

  • Creates deployment artifact
  • Installs dependencies
  • Compiles assets
  • Creates Docker image

Release Stage

  • Combines build with config
  • Runs database migrations
  • Collects static files
  • Executed via release.sh

Run Stage

  • Executes the application
  • Uses Procfile for process types
  • No code/config changes at runtime

Build command:

docker build -t myapp:v123 .

Release command:

./release.sh  # Migrations, collectstatic

Run command:

gunicorn config.wsgi:application

VI. Processes

Execute the app as stateless processes

Implementation:

  • All processes are stateless
  • No sticky sessions
  • Session data in Redis/database, not memory
  • File uploads go to object storage
  • Ephemeral filesystem
  • Process memory is transient

Stateless process example:

# ❌ BAD: In-memory session
sessions = {}

# ✅ GOOD: Database/Redis session
SESSION_ENGINE = "django.contrib.sessions.backends.cache"

VII. Port Binding

Export services via port binding

Implementation:

  • Self-contained web server (Gunicorn/Daphne)
  • No external web server in container
  • Port configurable via $PORT environment variable
  • Standalone HTTP services

Example:

# Procfile
web: gunicorn config.wsgi:application --bind 0.0.0.0:$PORT

VIII. Concurrency

Scale out via the process model

Implementation:

  • Horizontal scaling via process replication
  • Different process types in Procfile:
    • web: HTTP requests
    • worker: Background jobs (Celery)
    • beat: Scheduled tasks
  • Each process type scales independently
  • Process manager handles concurrency (Docker, K8s, Systemd)

Procfile:

web: gunicorn config.wsgi --workers 4
worker: celery -A config worker --concurrency=2
beat: celery -A config beat

Scaling:

# Scale web processes
docker-compose up --scale web=5

# Scale worker processes
docker-compose up --scale worker=10

IX. Disposability

Maximize robustness with fast startup and graceful shutdown

Implementation:

Fast Startup

  • Minimal initialization time
  • Lazy loading where possible
  • Connection pooling with timeouts

Graceful Shutdown

  • SIGTERM handler registered
  • Closes database connections
  • Finishes current requests
  • No orphaned transactions
  • Celery workers finish current tasks

Signal handler:

def graceful_shutdown(signum, frame):
    logger.info("Shutting down gracefully...")
    # Close connections
    for conn in connections.all():
        conn.close()
    sys.exit(0)

signal.signal(signal.SIGTERM, graceful_shutdown)

Crash Resilience

  • Process supervisor restarts crashed processes
  • Database transactions use ATOMIC_REQUESTS
  • Idempotent migrations

X. Dev/Prod Parity

Keep development, staging, and production as similar as possible

Implementation:

Time Gap: Minimized

  • Continuous deployment
  • Same-day code → production

Personnel Gap: Eliminated

  • Developers deploy their code
  • Infrastructure as code

Tools Gap: Identical

  • Same services in dev and prod:
    • PostgreSQL (not SQLite)
    • Redis
    • Same Python version
  • Docker ensures consistency
  • docker-compose.yml mirrors production

Example:

# Same services dev and prod
services:
  db:
    image: postgres:16
  redis:
    image: redis:7
  web:
    build: .

XI. Logs

Treat logs as event streams

Implementation:

  • All logs to stdout/stderr
  • No log files in application
  • Structured JSON logging in production
  • Log aggregation by execution environment
  • Stream-based log processing

Logging config:

LOGGING = {
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "json",  # Structured logs
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "INFO",
    },
}

Log aggregation:

# Logs captured by platform
# Render: Dashboard logs
# Kubernetes: kubectl logs
# Docker: docker logs
# Systemd: journalctl

XII. Admin Processes

Run admin/management tasks as one-off processes

Implementation:

  • Django management commands
  • Same environment as web processes
  • One-off process execution
  • No special admin servers

Examples:

# Database migration
python manage.py migrate

# Create superuser
python manage.py createsuperuser

# Custom data import
python manage.py import_data

# Django shell
python manage.py shell

# Run in production
heroku run python manage.py migrate
kubectl exec -it pod -- python manage.py shell
docker-compose run web python manage.py createsuperuser

Management command structure:

# apps/core/management/commands/import_data.py
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    def handle(self, *args, **options):
        # Admin task logic
        pass

Verification Checklist

Use this checklist to verify 12-factor compliance:

  • I. Codebase: Git repo? Multiple environments?
  • II. Dependencies: pyproject.toml? Lock file?
  • III. Config: Environment variables? No secrets in code?
  • IV. Backing Services: URLs? Swappable?
  • V. Build/Release/Run: Separate stages? Release script?
  • VI. Processes: Stateless? No sticky sessions?
  • VII. Port Binding: Self-contained? Configurable port?
  • VIII. Concurrency: Procfile? Horizontal scaling?
  • IX. Disposability: Fast startup? Graceful shutdown?
  • X. Dev/Prod Parity: Same services? Docker?
  • XI. Logs: stdout/stderr? Structured?
  • XII. Admin: Management commands? One-off processes?

References