Settings Per Environment

Structure settings so dev, staging and production diverge cleanly, secrets stay out of git, and 12-factor config comes from the environment.

A single settings.py with if DEBUG: branches scattered through it is a liability — it is how a debug toolbar or a test database ends up in production. Mature projects split configuration so each environment is explicit and secrets never touch version control.

Two proven approaches

  • A settings packagesettings/base.py holds the shared bulk; dev.py and prod.py each from .base import * and override. Select one with DJANGO_SETTINGS_MODULE=myproject.settings.prod.
  • 12-factor / env vars — one settings file that reads everything variable from the environment (os.environ, or a helper like django-environ). This is the cloud-native default and pairs well with the package split.

The non-negotiables

SECRET_KEY, database passwords, and API tokens come from the environment — never a literal in a committed file. DEBUG defaults to False and is opt-in, so a misconfigured box fails safe rather than leaking tracebacks. ALLOWED_HOSTS is set explicitly in production. Keep a .env.example in git documenting the required variables, and the real .env in .gitignore.

Validate early: read required settings at startup and raise a clear error if one is missing, so a deploy fails loudly at boot instead of mysteriously at the first request.

Example

Example · python
# settings/base.py  — everything shared, secrets pulled from the environment
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent.parent

def env(key, default=None, required=False):
    val = os.environ.get(key, default)
    if required and val is None:
        raise RuntimeError(f"Missing required setting: {key}")   # fail LOUD at boot
    return val

SECRET_KEY = env("DJANGO_SECRET_KEY", required=True)
# Fail-safe: only True when explicitly "1". Note: the string "False" is truthy!
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"
ALLOWED_HOSTS = env("DJANGO_ALLOWED_HOSTS", "").split(",") if env("DJANGO_ALLOWED_HOSTS") else []


# settings/dev.py
from .base import *          # noqa
DEBUG = True
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
INSTALLED_APPS += ["debug_toolbar"]


# settings/prod.py
from .base import *          # noqa
DEBUG = False               # never overridden to True here
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True


# Select per environment:
#   export DJANGO_SETTINGS_MODULE=myproject.settings.prod

When to use it

  • A developer uses django-environ to parse a DATABASE_URL string so settings.py contains no raw credentials.
  • A team uses a .env.example file checked into git and a .env file excluded via .gitignore for local overrides.
  • A DevOps engineer passes all secrets as environment variables in a Kubernetes Secret manifest so nothing is hard-coded.

More examples

Use django-environ for DATABASE_URL

Loads all settings from environment variables or a .env file using django-environ's typed API.

Example · python
# pip install django-environ
import environ

env = environ.Env(DEBUG=(bool, False))
environ.Env.read_env()  # reads .env file

DEBUG = env("DEBUG")
SECRET_KEY = env("SECRET_KEY")
DATABASES = {"default": env.db("DATABASE_URL")}

.env.example for onboarding

Commits a template .env.example with placeholder values so developers know which vars to set locally.

Example · bash
# .env.example  (commit this)
SECRET_KEY=change-me
DEBUG=True
DATABASE_URL=postgres://user:pass@localhost/mydb

# .gitignore
.env

Override settings per test

Uses @override_settings to swap the email backend to an in-memory one during unit tests.

Example · python
from django.test import TestCase, override_settings

@override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend")
class EmailTest(TestCase):
    def test_sends_welcome_email(self):
        from django.core import mail
        trigger_welcome_email("[email protected]")
        self.assertEqual(len(mail.outbox), 1)

Discussion

  • Be the first to comment on this lesson.
Settings Per Environment — Django | SoundsCode