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 packagesettings/base.py, settings/dev.py, settings/production.py, each importing from base. Select with DJANGO_SETTINGS_MODULE.
  • One settings file driven by env vars — using a helper like django-environ to read a .env locally 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

Example · python
# 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.

Example · bash
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 hasher

Read all secrets from env

Raises ImproperlyConfigured at startup if a required environment variable is missing, preventing silent failures.

Example · python
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.

Example · bash
# .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

  • Be the first to comment on this lesson.