Environment settings (after Django + uv)
Prerequisite: you finished django-uv.md — project runs, /api/health works, secrets still live in settings.py.
End state: SECRET_KEY, DEBUG, and ALLOWED_HOSTS come from a local .env file (not committed). settings.py reads them through the environment.
Suggested order among follow-ons: do this before Postgres, CORS, and Auth. Those steps all grow settings that should not be hard-coded. Second app does not depend on this.
How to use this: change one thing, then prove it still boots. Do not invent a settings framework beyond what is written here.
0. Why you are here
startproject puts a generated SECRET_KEY and DEBUG = True in source. That is fine for a private learning repo for an hour. It is not fine once the repo is shared, pushed, or deployed.
You are moving config that changes per machine out of git, not rewriting Django settings architecture.
1. Add django-environ
From the project root (where pyproject.toml is):
uv add django-environ
- [ ] Run that.
Why this package: thin wrapper around env vars with typed helpers (env.bool, env.list) and optional .env file loading. Plain os.environ also works; django-environ saves boilerplate and pairs cleanly with a later DATABASE_URL.
2. Create .env (local only)
In the project root, create .env:
DJANGO_SECRET_KEY=replace-me-with-a-long-random-string
DJANGO_DEBUG=True
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
Generate a better secret (paste the output into .env):
uv run python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
- [ ] File exists; secret is not the placeholder string.
Confirm .gitignore already contains .env (from the base runbook). If not, add it now.
- [ ]
git statusdoes not list.envas a tracked/new file you intend to commit (or it shows as ignored).
Why: .env is for your laptop. Production will inject the same variable names via the host (Railway, Fly, systemd, etc.) without that file.
3. Optional: commit a template
Create .env.example (this one is committed):
DJANGO_SECRET_KEY=
DJANGO_DEBUG=True
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
- [ ] Save it.
Future you (or a teammate) copies it to .env and fills values. No secrets in the example file.
4. Teach settings.py to read the env
Near the top of config/settings.py, after the usual from pathlib import Path, add:
import environ
BASE_DIR = Path(__file__).resolve().parent.parent
env = environ.Env(
DJANGO_DEBUG=(bool, False),
DJANGO_ALLOWED_HOSTS=(list, []),
)
environ.Env.read_env(BASE_DIR / ".env")
If BASE_DIR was already defined, do not duplicate it — only insert the environ bits around the existing BASE_DIR.
Replace the hard-coded settings:
SECRET_KEY = env("DJANGO_SECRET_KEY")
DEBUG = env("DJANGO_DEBUG")
ALLOWED_HOSTS = env("DJANGO_ALLOWED_HOSTS")
- [ ] Remove the old literal
SECRET_KEY = "django-insecure-…"andDEBUG = True/ emptyALLOWED_HOSTSassignments you replaced. - [ ] Save.
Notes:
env("DJANGO_SECRET_KEY")with no default fails loudly if missing — what you want.DJANGO_DEBUG=(bool, False)means missing/invalid debug defaults toFalse(safer than defaulting toTrue).ALLOWED_HOSTSas a list reads comma-separated values from the env string.
5. Prove it
uv run python manage.py check
uv run python manage.py runserver
- [ ]
checkreports no issues. - [ ] Server starts;
/api/healthstill returns JSON.
Temporarily break .env (rename DJANGO_SECRET_KEY line) and run check again — you should get an error about a missing variable. Put it back.
- [ ] Confirmed missing secret fails; restored
.envworks.
Checkpoint — you are done when
- [ ] Secrets/debug/hosts are not hard-coded in
settings.py - [ ]
.envis gitignored;.env.example(if you made it) is committed without real secrets - [ ] App boots with values from
.env
Stop. Do not add Redis/email/S3 settings “while you are here.” Add env keys when a later runbook needs them.
If you get stuck
| Symptom | Likely cause |
|---|---|
ImproperlyConfigured / missing DJANGO_SECRET_KEY |
.env missing, wrong directory, or typo in the name |
DEBUG always False |
Value not True/False as django-environ expects, or .env not loaded |
| DisallowedHost | DJANGO_ALLOWED_HOSTS missing the host you used in the browser |
.env shows up in git status as untracked to add |
Not listed in .gitignore |
Next
- Need a real database →
django-postgres.md(addsDATABASE_URLto this same.envpattern) - Frontend on another origin →
django-cors.md - Protect routes →
django-ninja-auth.md