Module 5 · Architecture and maintainable code

Lesson 27 — Environment configuration (12-factor)

Secrets and settings per environment: the same image, three deployments.

Published
In this lesson
  1. Exercise 1 — Configuration audit
  2. Exercise 2 — 12-factor settings.py
  3. Exercise 3 — Dual rotation
  4. Exercise 4 — Fail-fast
  5. Exercise 5 — Config as feature flags
  6. Submit

Migrate TicketFlow's settings to the environment. No solutions.md before submitting.

Exercise 1 — Configuration audit

  1. List ALL your project's config: what sits in settings.py as literals, what reads from env vars, what lives in view/service code (a requests.get("https://...") with a hardcoded URL is hidden config).
  2. Classify each: code (doesn't vary), sensitive config (a secret), non-sensitive config (varies but isn't secret), and bad config (a literal that MUST vary and is nailed down).
  3. Move the bad config to the environment with safe defaults. The test: git grep -n "https://" -- '*.py' returns only documentation URLs.

Exercise 2 — 12-factor settings.py

  1. Convert your settings.py into a single module with django-environ (or pydantic-settings): SECRET_KEY/DEBUG/ALLOWED_HOSTS/DATABASE_URL/REDIS_URL from the environment, safe defaults.
  2. Write the .env.example (no real values) and the dev .env (gitignored). A teammate must be able to clone and boot with just cp.env.example.env + local values.
  3. The same-image proof: write the Dockerfile (40 goes deeper) with ONE image and boot 3 containers with 3.env files. Same image hash, three configs.

Exercise 3 — Dual rotation

  1. Implement SECRET_KEYS (a list with new + optional old) and make the token/simplejwt validator accept any key in the list.
  2. Write the rotation sequence as a 5-step checklist with the time window between step 2 (dual deploy) and 5 (retiring the old one). What happens to tokens issued with the old key during the window?
  3. Do the same for 17's WEBHOOK_SECRET (HMAC verification accepts the new and the old key).

Exercise 4 — Fail-fast

  1. Add the boot validations (SECRET_KEY, ALLOWED_HOSTS, DATABASE_URL mandatory outside DEBUG) with ImproperlyConfigured and messages saying WHICH variable is missing and WHERE it is documented.
  2. Smoke test: manage.py check --deploy with a fake prod environment (vars set in the test) passes; with DJANGO_DEBUG=true in fake prod, it fails with a clear error.
  3. Document in the README the "boot triage": the 3 most commonly missing variables and their exact symptom at boot.

Exercise 5 — Config as feature flags

  1. Add FEATURE_WEBHOOKS (bool) and FEATURE_SAGA_V2 (bool) as config flags (not code flags): read from the environment, default false, used with if settings.FEATURE_WEBHOOKS: in the service.
  2. Write the test that runs the same test with the flag on and off. What does this gain over a git branch per environment?

Submit

Paste the settings.py, the.env.example, the rotation checklist and the smoke test. Next: Lesson 28 — Refactoring without breaking anything.