Environment-Specific Configuration¶
Learn how to manage different configurations across development, staging, and production environments using Django settings patterns and environment variables.
Overview¶
Different environments require different configurations:
Development: Debug enabled, fewer workers, verbose logging
Staging: Production-like settings for testing
Production: Optimized for performance and security
Django Settings Patterns¶
Pattern 1: Separate Settings Files¶
The most common approach - separate files for each environment.
Directory Structure¶
myproject/
├── settings/
│ ├── __init__.py
│ ├── base.py # Shared settings
│ ├── dev.py # Development
│ ├── staging.py # Staging
│ └── prod.py # Production
└── manage.py
Base Settings¶
# myproject/settings/base.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
# Apps common to all environments
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
# ... other apps
'django_prodserver',
]
# Middleware, templates, etc.
MIDDLEWARE = [...]
TEMPLATES = [...]
# Default database (override in environment files)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'myapp',
}
}
Development Settings¶
# myproject/settings/dev.py
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
# Development database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Simple dev server
PRODUCTION_PROCESSES = {
"web": {
"BACKEND": "django_prodserver.backends.servers.waitress.WaitressServer",
"ARGS": {
"host": "127.0.0.1",
"port": "8000",
"threads": "2",
}
}
}
# Email to console
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Staging Settings¶
# myproject/settings/staging.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = ['staging.example.com']
# Staging database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'myapp_staging',
'USER': 'myapp',
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': 'staging-db.example.com',
'PORT': '5432',
}
}
# Staging configuration - similar to production but with debug features
PRODUCTION_PROCESSES = {
"web": {
"BACKEND": "django_prodserver.backends.servers.gunicorn.GunicornServer",
"ARGS": {
"bind": "0.0.0.0:8000",
"workers": "2",
"timeout": "60",
"loglevel": "debug", # More verbose logging
}
},
"worker": {
"BACKEND": "django_prodserver.backends.workers.celery.CeleryWorker",
"APP": "myproject.celery.app",
"ARGS": {
"concurrency": "2",
"loglevel": "debug",
}
}
}
# Celery configuration
CELERY_BROKER_URL = os.getenv('CELERY_BROKER_URL', 'redis://staging-redis:6379/0')
Production Settings¶
# myproject/settings/prod.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
# Production database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST'),
'PORT': os.getenv('DB_PORT', '5432'),
'CONN_MAX_AGE': 600,
}
}
# Production server configuration
PRODUCTION_PROCESSES = {
"web": {
"BACKEND": "django_prodserver.backends.servers.gunicorn.GunicornServer",
"ARGS": {
"bind": "0.0.0.0:8000",
"workers": "8",
"worker-class": "gevent",
"timeout": "120",
"max-requests": "1000",
"max-requests-jitter": "100",
"access-logfile": "/var/log/myapp/access.log",
"error-logfile": "/var/log/myapp/error.log",
}
},
"worker": {
"BACKEND": "django_prodserver.backends.workers.celery.CeleryWorker",
"APP": "myproject.celery.app",
"ARGS": {
"concurrency": "8",
"loglevel": "info",
"max-tasks-per-child": "1000",
}
},
"beat": {
"BACKEND": "django_prodserver.backends.workers.celery.CeleryBeat",
"APP": "myproject.celery.app",
"ARGS": {
"loglevel": "info",
}
}
}
# Celery configuration
CELERY_BROKER_URL = os.getenv('CELERY_BROKER_URL')
CELERY_RESULT_BACKEND = os.getenv('CELERY_RESULT_BACKEND')
# Security settings
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
Usage¶
# Development
python manage.py runserver --settings=myproject.settings.dev
# Staging
python manage.py server web --settings=myproject.settings.staging
# Production
python manage.py server web --settings=myproject.settings.prod
Or set the DJANGO_SETTINGS_MODULE environment variable:
# In your shell or systemd service file
export DJANGO_SETTINGS_MODULE=myproject.settings.prod
python manage.py server web
Pattern 2: Single File with Environment Variables¶
Use a single settings file with environment variable controls.
# settings.py
import os
# Environment selection
ENV = os.getenv('DJANGO_ENV', 'development')
DEBUG = ENV == 'development'
if ENV == 'production':
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '').split(',')
else:
ALLOWED_HOSTS = ['*']
# Database configuration from environment
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME', 'myapp'),
'USER': os.getenv('DB_USER', 'myapp'),
'PASSWORD': os.getenv('DB_PASSWORD', ''),
'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': os.getenv('DB_PORT', '5432'),
}
}
# Environment-specific configurations
if ENV == 'production':
WEB_WORKERS = os.getenv('WEB_WORKERS', '8')
WORKER_CONCURRENCY = os.getenv('WORKER_CONCURRENCY', '8')
elif ENV == 'staging':
WEB_WORKERS = os.getenv('WEB_WORKERS', '4')
WORKER_CONCURRENCY = os.getenv('WORKER_CONCURRENCY', '4')
else: # development
WEB_WORKERS = os.getenv('WEB_WORKERS', '2')
WORKER_CONCURRENCY = os.getenv('WORKER_CONCURRENCY', '2')
PRODUCTION_PROCESSES = {
"web": {
"BACKEND": "django_prodserver.backends.servers.gunicorn.GunicornServer",
"ARGS": {
"bind": f"0.0.0.0:{os.getenv('PORT', '8000')}",
"workers": WEB_WORKERS,
"timeout": os.getenv('WEB_TIMEOUT', '60'),
}
},
"worker": {
"BACKEND": "django_prodserver.backends.workers.celery.CeleryWorker",
"APP": "myproject.celery.app",
"ARGS": {
"concurrency": WORKER_CONCURRENCY,
}
}
}
Environment Variables Management¶
Using python-dotenv¶
Install python-dotenv:
pip install python-dotenv
Create environment files:
# .env.development
DJANGO_ENV=development
DEBUG=True
SECRET_KEY=dev-secret-key
DATABASE_URL=sqlite:///db.sqlite3
# .env.production
DJANGO_ENV=production
DEBUG=False
SECRET_KEY=production-secret-key
DATABASE_URL=postgresql://user:pass@host/db
ALLOWED_HOSTS=example.com,www.example.com
WEB_WORKERS=8
WORKER_CONCURRENCY=8
Load in settings:
# settings.py
from pathlib import Path
from dotenv import load_dotenv
import os
# Load environment file
ENV_FILE = Path(__file__).resolve().parent.parent / f'.env.{os.getenv("DJANGO_ENV", "development")}'
load_dotenv(ENV_FILE)
# Now use os.getenv() as normal
DEBUG = os.getenv('DEBUG', 'False') == 'True'
Using django-environ¶
pip install django-environ
# settings.py
import environ
env = environ.Env(
DEBUG=(bool, False),
ALLOWED_HOSTS=(list, []),
)
# Read .env file
environ.Env.read_env()
DEBUG = env('DEBUG')
ALLOWED_HOSTS = env('ALLOWED_HOSTS')
DATABASES = {
'default': env.db(),
}
PRODUCTION_PROCESSES = {
"web": {
"BACKEND": "django_prodserver.backends.servers.gunicorn.GunicornServer",
"ARGS": {
"bind": f"0.0.0.0:{env('PORT', default=8000)}",
"workers": env('WEB_WORKERS', default='4'),
}
}
}
Configuration Examples by Environment¶
Development Configuration¶
Focus: Easy debugging, fast iteration
DEBUG = True
ALLOWED_HOSTS = ['*']
PRODUCTION_PROCESSES = {
"web": {
"BACKEND": "django_prodserver.backends.servers.waitress.WaitressServer",
"ARGS": {
"host": "127.0.0.1",
"port": "8000",
"threads": "2",
}
},
# Lightweight task queue for development
"worker": {
"BACKEND": "django_prodserver.backends.workers.django_tasks.DjangoTasksWorker",
"ARGS": {
"processes": "1",
}
}
}
# Console email backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# SQLite for simplicity
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
Staging Configuration¶
Focus: Production-like testing environment
DEBUG = False
ALLOWED_HOSTS = ['staging.example.com']
PRODUCTION_PROCESSES = {
"web": {
"BACKEND": "django_prodserver.backends.servers.gunicorn.GunicornServer",
"ARGS": {
"bind": "0.0.0.0:8000",
"workers": "4",
"timeout": "60",
"loglevel": "debug",
"access-logfile": "-", # Log to stdout
}
},
"worker": {
"BACKEND": "django_prodserver.backends.workers.celery.CeleryWorker",
"APP": "myproject.celery.app",
"ARGS": {
"concurrency": "4",
"loglevel": "debug",
}
},
"beat": {
"BACKEND": "django_prodserver.backends.workers.celery.CeleryBeat",
"APP": "myproject.celery.app",
"ARGS": {
"loglevel": "debug",
}
}
}
Production Configuration¶
Focus: Performance, security, reliability
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
PRODUCTION_PROCESSES = {
"web": {
"BACKEND": "django_prodserver.backends.servers.uvicorn.UvicornServer",
"ARGS": {
"host": "0.0.0.0",
"port": "8000",
"workers": "8",
"loop": "uvloop",
"limit-concurrency": "1000",
"log-level": "warning",
}
},
"worker": {
"BACKEND": "django_prodserver.backends.workers.celery.CeleryWorker",
"APP": "myproject.celery.app",
"ARGS": {
"concurrency": "8",
"loglevel": "info",
"max-tasks-per-child": "1000",
"max-memory-per-child": "500000",
}
},
"beat": {
"BACKEND": "django_prodserver.backends.workers.celery.CeleryBeat",
"APP": "myproject.celery.app",
"ARGS": {
"loglevel": "info",
}
}
}
# Security settings
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Best Practices¶
Never commit secrets: Use environment variables for sensitive data
Use .gitignore: Exclude .env files from version control
Document environment variables: Create .env.example as template
Test staging like production: Make staging as close to production as possible
Use consistent naming: Follow naming conventions for environment variables
Validate configuration: Check required settings on startup
Monitor configuration changes: Log configuration on startup (excluding secrets)