How do you give a Django project different settings for development, staging and production, and how does Django decide which settings module to load?
answer
- one environment variable names it
- setdefault in manage.py and wsgi.py
- base module plus overrides
- environment variables are strings
- fail fast on missing secrets
basics
~20 sDjango loads the settings module named by the DJANGO_SETTINGS_MODULE environment variable, or a command's --settings option. Per-environment setups use a shared base module with small dev, staging and prod modules, or one module reading environment variables, with secrets always from the environment.
solid answer
~40 sDjango imports whichever module `DJANGO_SETTINGS_MODULE` names. The generated `manage.py`, `wsgi.py` and `asgi.py` call `os.environ.setdefault("DJANGO_SETTINGS_MODULE", "payroll.settings")`, so a real environment variable wins, and every management command also accepts `--settings`. Two patterns are common: a settings package with `base.py` plus `dev.py`, `staging.py` and `prod.py` that do `from .base import *` and override a few values, or one `settings.py` reading everything that differs from `os.environ`. Either way, secrets such as `SECRET_KEY` and database passwords come from the environment, never the repository. Read them with `os.environ["DJANGO_SECRET_KEY"]` so a missing value fails at startup; parse strings deliberately, because `ALLOWED_HOSTS` must be a list and `bool("False")` is `True`.
code
python · 18 lines# payroll/settings/base.py (excerpt)
import os
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] # KeyError at startup if missing
DEBUG = os.environ.get("DJANGO_DEBUG", "") == "1"
ALLOWED_HOSTS = [
h.strip() for h in os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",") if h.strip()
]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["PAYROLL_DB_NAME"],
"USER": os.environ["PAYROLL_DB_USER"],
"PASSWORD": os.environ["PAYROLL_DB_PASSWORD"],
"HOST": os.environ.get("PAYROLL_DB_HOST", "localhost"),
"PORT": int(os.environ.get("PAYROLL_DB_PORT", "5432")),
}
}go deeper
Know that DJANGO_SETTINGS_MODULE chooses the settings module and that secrets belong in environment variables, not the repository.
Explain setdefault in manage.py and wsgi.py, the --settings option, and the base-plus-overrides versus single-module patterns.
Parse environment strings strictly, fail fast on missing secrets, and keep staging close enough to production that it catches configuration bugs.
Choose a configuration policy for many services: one artifact driven by environment, documented variables, and defaults that can only fail closed.
## How Django picks the settings module Django has exactly one active settings module per process, and it finds it through the **`DJANGO_SETTINGS_MODULE`** environment variable, which holds a dotted Python path such as `payroll.settings.prod`. - `manage.py`, `wsgi.py` and `asgi.py` generated by `startproject` call `os.environ.setdefault("DJANGO_SETTINGS_MODULE", "payroll.settings")`. Because it is `setdefault`, a variable already set in the environment **wins** over the hard-coded default. - Every management command accepts **`--settings payroll.settings.staging`**, which sets the variable for that run. - Scripts and tools that run outside those entry points must set the variable, or call `settings.configure()`. The module is imported lazily the first time a setting is read, and Django overlays its uppercase names on the defaults in `global_settings.py`. ## Pattern 1: a base module plus per-environment modules ```text payroll/settings/ __init__.py base.py # everything shared: INSTALLED_APPS, MIDDLEWARE, TEMPLATES dev.py # from .base import *; DEBUG = True; local database staging.py # from .base import *; staging hosts; real email disabled prod.py # from .base import *; DEBUG = False; production hosts ``` - **Pros:** differences are visible in code review; each file is short. - **Cons:** star imports hide where a name came from, and it is easy for staging and prod to drift. ## Pattern 2: one module driven by environment variables A single `settings.py` reads every environment-specific value from `os.environ`. The same artifact runs everywhere, and only the environment differs. - **Pros:** one code path; staging really tests production settings. - **Cons:** the settings module must parse and validate strings carefully. Many teams combine them: a base module plus a thin production module, with all secrets and hostnames in environment variables either way. ## Reading environment variables correctly Environment variables are always **strings**, and Django validates only a few settings: | Setting | Pitfall | Safer form | |---|---|---| | `SECRET_KEY` | a silent default ships a known key | `os.environ["DJANGO_SECRET_KEY"]` raises `KeyError` at startup if missing | | `DEBUG` | `bool("False")` is `True` | `os.environ.get("DJANGO_DEBUG", "") == "1"` | | `ALLOWED_HOSTS` | a raw string raises `ImproperlyConfigured: The ALLOWED_HOSTS setting must be a list or a tuple.` | split on commas and drop empty items | | database port or timeouts | strings where numbers are expected | convert with `int()` explicitly | Django also refuses an empty key: reading `settings.SECRET_KEY` when it is empty raises `ImproperlyConfigured: The SECRET_KEY setting must not be empty.` Fail-fast reads turn a misconfigured deployment into a crash at startup instead of a subtly insecure service. ## Rules that keep the split sane 1. **Production-safe defaults** — a missing variable should make the service stricter (`DEBUG` off), never looser. 2. **No secrets in the repository**, not even "only for dev"; generate a dev key locally. 3. **Do not import `global_settings`** in a settings file; Django applies the defaults for you, and the docs say a settings file should not import it. 4. **Assign settings only in settings modules**; the docs warn against changing them at runtime. 5. **Keep the list of environment variables documented** next to the settings, so a new environment can be built from it. ## What an interviewer is listening for A middle candidate names `DJANGO_SETTINGS_MODULE`, knows the `setdefault` in `manage.py` and `wsgi.py`, and describes one of the two patterns. A stronger one covers string parsing, fail-fast secrets, and why production-safe defaults matter.
- Why does manage.py use os.environ.setdefault rather than assigning the variable?`setdefault` only fills in the variable when it is not already set, so a deployment can export `DJANGO_SETTINGS_MODULE=payroll.settings.prod` and override the development default baked into `manage.py` and `wsgi.py`. Assigning it unconditionally would silently ignore the environment and load the wrong settings.
- What goes wrong with os.environ.get('DJANGO_SECRET_KEY', 'dev-key')?A missing variable in production silently falls back to a key committed to the repository, so anyone with the code can forge signed values. Reading with `os.environ[...]` in production settings makes the process fail at startup instead, which is the failure you want.
saying these in an interview costs you the question
- Commits a real SECRET_KEY to the repository for convenience
- Assigns ALLOWED_HOSTS the raw comma-separated environment string
- Gives SECRET_KEY a hard-coded fallback in production settings
- Believes the setdefault in manage.py overrides the real environment
- Imports global_settings into a settings module to extend defaults