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:
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
Procfilefor process types - No code/config changes at runtime
Build command:
Release command:
Run command:
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:
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.ymlmirrors production
Example:
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:
- [x] I. Codebase: Git repo? Multiple environments?
- [x] II. Dependencies:
pyproject.toml? Lock file? - [x] III. Config: Environment variables? No secrets in code?
- [x] IV. Backing Services: URLs? Swappable?
- [x] V. Build/Release/Run: Separate stages? Release script?
- [x] VI. Processes: Stateless? No sticky sessions?
- [x] VII. Port Binding: Self-contained? Configurable port?
- [x] VIII. Concurrency: Procfile? Horizontal scaling?
- [x] IX. Disposability: Fast startup? Graceful shutdown?
- [x] X. Dev/Prod Parity: Same services? Docker?
- [x] XI. Logs: stdout/stderr? Structured?
- [x] XII. Admin: Management commands? One-off processes?