Django Keel strictly adheres to the 12-Factor App methodology. This document details how each factor is implemented.
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
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.lockorpoetry.lock) for reproducibility
Example:
dependencies = [
"django>=5.2,<7.0",
"psycopg[binary]>=3.2.0",
"gunicorn>=22.0.0",
]Store config in the environment
✅ Implementation:
- All configuration via environment variables
- No secrets in code
django-environfor structured config.env.examplefor 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)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 environmentStrictly separate build and run stages
✅ Implementation:
- Creates deployment artifact
- Installs dependencies
- Compiles assets
- Creates Docker image
- Combines build with config
- Runs database migrations
- Collects static files
- Executed via
release.sh
- Executes the application
- Uses
Procfilefor process types - No code/config changes at runtime
Build command:
docker build -t myapp:v123 .Release command:
./release.sh # Migrations, collectstaticRun command:
gunicorn config.wsgi:applicationExecute 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"Export services via port binding
✅ Implementation:
- Self-contained web server (Gunicorn/Daphne)
- No external web server in container
- Port configurable via
$PORTenvironment variable - Standalone HTTP services
Example:
# Procfile
web: gunicorn config.wsgi:application --bind 0.0.0.0:$PORTScale out via the process model
✅ Implementation:
- Horizontal scaling via process replication
- Different process types in
Procfile:web: HTTP requestsworker: 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=10Maximize robustness with fast startup and graceful shutdown
✅ Implementation:
- Minimal initialization time
- Lazy loading where possible
- Connection pooling with timeouts
- 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)- Process supervisor restarts crashed processes
- Database transactions use
ATOMIC_REQUESTS - Idempotent migrations
Keep development, staging, and production as similar as possible
✅ Implementation:
- Continuous deployment
- Same-day code → production
- Developers deploy their code
- Infrastructure as code
- Same services in dev and prod:
- PostgreSQL (not SQLite)
- Redis
- Same Python version
- Docker ensures consistency
docker-compose.ymlmirrors production
Example:
# Same services dev and prod
services:
db:
image: postgres:16
redis:
image: redis:7
web:
build: .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: journalctlRun 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 createsuperuserManagement 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
passUse 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?