Settings Per Environment
Structure settings for dev, staging and production without leaking secrets or shipping DEBUG=True.
A single settings.py with if DEBUG: branches everywhere is how secrets end up in git and how DEBUG=True ends up in production. Seniors separate configuration that differs by environment (database URLs, secret keys, allowed hosts) from code, and read the former from the environment.
Two solid approaches
- A settings package —
settings/base.py,settings/dev.py,settings/production.py, each importing from base. Select withDJANGO_SETTINGS_MODULE. - One settings file driven by env vars — using a helper like
django-environto read a.envlocally and real environment variables in production. This is the twelve-factor style.
import os
# Fail loudly if a required secret is missing — never fall back to a default.
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
DEBUG = os.environ.get('DJANGO_DEBUG', '') == '1'
ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',')The golden rule: secrets never live in the repo. Commit a .env.example with blank values as documentation, add .env to .gitignore, and inject real values through your host's secret manager.
Example
# settings/base.py — everything common lives here.
from pathlib import Path
import environ
BASE_DIR = Path(__file__).resolve().parent.parent.parent
env = environ.Env(DEBUG=(bool, False))
environ.Env.read_env(BASE_DIR / '.env') # local dev only; prod uses real env vars
SECRET_KEY = env('DJANGO_SECRET_KEY')
DEBUG = env('DEBUG')
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=[])
DATABASES = {'default': env.db('DATABASE_URL')} # parses postgres://user:pass@host/db
CACHES = {'default': env.cache('REDIS_URL', default='locmemcache://')}
# settings/dev.py
from .base import * # noqa
DEBUG = True
ALLOWED_HOSTS = ['*']
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware', *MIDDLEWARE]
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# settings/production.py
from .base import * # noqa
DEBUG = False
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
STORAGES = {'default': {'BACKEND': 'storages.backends.s3.S3Storage'}}
# manage.py / wsgi.py select the module:
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production')When to use it
- A team uses a settings package with base.py, local.py, and production.py to share common config and separate secrets.
- A developer reads all secrets from environment variables so the codebase contains no hard-coded credentials.
- A DevOps engineer sets DJANGO_SETTINGS_MODULE in a Docker environment file to switch settings per deployment target.
More examples
Settings package layout
Shows the recommended directory layout for splitting Django settings across environments.
mysite/settings/
__init__.py
base.py # shared across all envs
local.py # from .base import *; DEBUG=True
production.py # from .base import *; DEBUG=False
testing.py # from .base import *; fast password hasherRead all secrets from env
Raises ImproperlyConfigured at startup if a required environment variable is missing, preventing silent failures.
import os
from django.core.exceptions import ImproperlyConfigured
def get_env(var_name):
try:
return os.environ[var_name]
except KeyError:
raise ImproperlyConfigured(f"Set the {var_name} environment variable")
SECRET_KEY = get_env("DJANGO_SECRET_KEY")
DATABASES = {"default": {"NAME": get_env("DB_NAME"), ...}}Select settings via env var
Sets DJANGO_SETTINGS_MODULE to pick the correct settings file per deployment environment.
# .env for local development
DJANGO_SETTINGS_MODULE=mysite.settings.local
# Docker / CI
export DJANGO_SETTINGS_MODULE=mysite.settings.production
python manage.py migrate
gunicorn mysite.wsgi:application
Discussion