Filter concepts by levelShowing all levels.

Django · Settings and Configuration

Configuration strategy

Concepts
1

Where a configuration value should actually live — environment variables, a secret manager, or a per-environment settings file — and how to keep the unsafe case from being the silent default.

This section

Strategy

Twelve-factor configuration: settings.py as code, values as environment.

Configuration strategy

coreintermediate

Read secrets and per-environment values from environment variables (or a secret manager), never hard-code them; split settings by environment (base/development/production); and keep defaults safe — a missing override should fail closed, not fall back to something insecure.

Think of it as

Twelve-factor configuration treats settings.py as code (checked into git, identical everywhere) and treats the actual values — a database password, a secret key, which environment this is — as environment, injected at runtime. The moment a real secret is typed as a literal string in settings.py, it stops being config and becomes a leak waiting for the next `git log`.

bash
# .env — local only, gitignored
DJANGO_SECRET_KEY=dev-only-not-for-production
DJANGO_DEBUG=True
DATABASE_URL=postgres://localhost/mysite_dev

What we're doing: Structure a settings package so shared config lives once and each environment overrides only what genuinely differs.

settings/production.pypython
from .base import *  # noqa: F403
import os

DEBUG = False
ALLOWED_HOSTS = os.environ["DJANGO_ALLOWED_HOSTS"].split(",")
DATABASES["default"] = {
    "ENGINE": "django.db.backends.postgresql",
    "NAME": os.environ["DB_NAME"],
}
1
Everything not overridden here — INSTALLED_APPS, MIDDLEWARE, TEMPLATES — is inherited unchanged from base.py, so there is exactly one place those are defined.
4
DEBUG is hard-coded False here rather than read from an environment variable — a production file is exactly the place a safe value is worth pinning, not leaving to a possibly-missing env var.

Why this works: Splitting settings this way means a value that should never differ (INSTALLED_APPS) physically cannot drift between environments, while a value that must differ (DATABASES, ALLOWED_HOSTS) is forced to be explicitly set in each environment's own file — the file structure itself prevents the two kinds of mistake from being interchangeable.

Falling back to an insecure default when an environment variable is missing

Wrong

python
DEBUG = os.environ.get("DJANGO_DEBUG", "True") == "True"
# missing env var silently means DEBUG = True in "production"

Better

python
DEBUG = os.environ.get("DJANGO_DEBUG", "False") == "True"
# missing env var safely means DEBUG = False

What you see: A production deployment that forgot to set DJANGO_DEBUG runs with full debug tracebacks exposed to every visitor, discovered only after the fact.

Why: os.environ.get(key, default) silently returns default for a missing variable — there is no error to notice. Choosing the safe value ("False") as that default means a forgotten environment variable fails toward security; choosing the convenient value ("True", matching local development) means the exact same mistake fails toward exposure.

Where each kind of configuration value should live

base.py

INSTALLED_APPS, MIDDLEWARE — shared, never duplicated

development.py / production.py

override only what genuinely differs

environment variable / secret manager

SECRET_KEY, DB password — never a settings.py literal

  1. base.py — INSTALLED_APPS, MIDDLEWARE — shared, never duplicated
  2. development.py / production.py — override only what genuinely differs
  3. environment variable / secret manager — SECRET_KEY, DB password — never a settings.py literal

Where each kind of configuration value should live

Where each kind of configuration value should live
ValueBelongs inNot in
SECRET_KEY, DB passwordenvironment variable / secret managersettings.py literal, git
DEBUG, ALLOWED_HOSTSper-environment settings file or env vara single shared value for all environments
INSTALLED_APPS, MIDDLEWAREbase.py, sharedduplicated across development.py/production.py
Third-party API keysenvironment variable / secret managersettings.py literal, git
Local-only overrides.env, gitignoredcommitted anywhere

Together

python
# settings/base.py
import os

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]  # required — raises if missing
DEBUG = os.environ.get("DJANGO_DEBUG", "False") == "True"  # safe default

Remember: Secrets and per-environment values come from the environment (or a secret manager), never a settings.py literal; a missing required value should raise, and a missing optional one should default to the SAFE choice, not the convenient one.

See also: settings · cookie and host security settings · settings py

Advertisement