Настройки Django для разных окружений: ошибки и как их избежать
Один settings.py на dev и prod рано или поздно роняет продакшн или утекает SECRET_KEY в репозиторий. Разбираем четыре подхода к разделению настроек и цену каждого.

Классический сценарий: разработчик держит один settings.py, где DEBUG = True, база — локальный SQLite, а SECRET_KEY прописан прямо в файле и закоммичен. На проде кто-то забывает переключить DEBUG, и первая же необработанная ошибка отдаёт пользователю полный traceback с путями, переменными окружения и фрагментами кода. Или наоборот: локально приложение работает, а на сервере падает с DisallowedHost, потому что ALLOWED_HOSTS пуст.
Проблема не в самих настройках, а в том, что dev и prod требуют разных значений одних и тех же параметров, а способ их разделения выбирают наугад. Ниже — четыре рабочих подхода, чем каждый платит и когда какой брать. Ориентируюсь на Django 5.x, но всё написанное работает и на 4.2 LTS.
Что вообще должно отличаться между окружениями
Прежде чем городить структуру, полезно понять, каких параметров это касается. Их немного, и они предсказуемы:
- DEBUG —
Trueлокально,Falseна проде. Самая частая и самая дорогая ошибка. - SECRET_KEY — разный для каждого окружения, никогда не в репозитории.
- ALLOWED_HOSTS — локально
['localhost', '127.0.0.1'], на проде реальный домен. - DATABASES — SQLite или локальный Postgres против боевого сервера БД.
- Почта, кеш, S3, ключи внешних сервисов — заглушки против реальных credential'ов.
- Логирование — консоль локально, файлы или внешний коллектор на проде.
Всё остальное (INSTALLED_APPS, MIDDLEWARE, шаблоны) обычно общее. Значит, задача сводится к тому, чтобы общее описать один раз, а различающееся вынести.
Подход 1: переменные окружения и один settings.py
Самый простой вариант: файл настроек остаётся один, но все различающиеся значения читаются из окружения. Секретов в коде нет, поведение меняется через переменные, которые задаёт сервер или контейнер.
# settings.py (Django 5.x)
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# Падаем сразу, если ключ не задан — лучше, чем тихий дефолт
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
# По умолчанию DEBUG выключен, включаем явно только в dev
DEBUG = os.environ.get("DJANGO_DEBUG", "") == "1"
ALLOWED_HOSTS = os.environ.get(
"DJANGO_ALLOWED_HOSTS", ""
).split(",")
Обратите внимание: SECRET_KEY читается через os.environ[...], а не .get() с дефолтом. Если переменная не задана, приложение не запустится — это правильно. Тихий дефолт SECRET_KEY = "changeme" — потенциальная дыра, потому что он молча уедет на прод.
Значение по умолчанию для секрета опаснее его отсутствия: отсутствие роняет запуск сразу, а дефолт живёт незаметно до первого инцидента.
Для чтения типизированных переменных удобно взять библиотеку django-environ или python-decouple — они разбирают DATABASE_URL, приводят строки к bool и int, читают .env-файл. Проверяйте актуальные имена методов в документации конкретной версии пакета: у django-environ это env.bool(), env.db(), но подтвердите по README установленной версии.
Чем платит
Когда различий много и они сложные (разные INSTALLED_APPS для dev с django-debug-toolbar, разные бэкенды кеша), один файл распухает от if DEBUG: и становится трудночитаемым. Тогда пора к подходу 2.
Подход 2: пакет settings с базой и наследниками
Вместо файла settings.py делаем пакет settings/ с общей базой и файлами под каждое окружение. Каждый наследник импортирует базу и переопределяет своё.
myproject/
settings/
__init__.py
base.py # общие настройки
development.py # DEBUG=True, debug_toolbar, локальная БД
production.py # DEBUG=False, боевые значения
# settings/development.py
from .base import * # noqa
DEBUG = True
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
Какой файл использовать, Django узнаёт из переменной DJANGO_SETTINGS_MODULE. Локально — myproject.settings.development, на проде — myproject.settings.production. Её задают в manage.py, WSGI/ASGI-модуле или в окружении сервера.
Даже при этом подходе секреты всё равно читаются из окружения — файл production.py не должен содержать реальный SECRET_KEY или пароль от БД. Разделение файлов решает вопрос структуры, а не вопрос секретов. Это две независимые задачи, их часто путают.
Чем платит
Использование from .base import * ломает статический анализ: линтер не видит, откуда пришло имя, отсюда # noqa. И порядок важен — если наследник забыл переопределить ALLOWED_HOSTS, приложение возьмёт значение из базы, которое может оказаться пустым.
Подход 3: django-environ и .env-файл
Компромисс между первыми двумя. Структура настроек может быть и одним файлом, и пакетом, но все значения приходят из .env, который не коммитится, а в репозитории лежит .env.example с ключами без значений.
# settings.py
import environ
env = environ.Env(DEBUG=(bool, False))
environ.Env.read_env(BASE_DIR / ".env") # читает .env, если он есть
SECRET_KEY = env("DJANGO_SECRET_KEY")
DEBUG = env("DEBUG")
DATABASES = {"default": env.db()} # разбирает DATABASE_URL
Плюс — единый способ описать конфигурацию и для локали, и для контейнера: dev читает .env из файла, prod получает те же переменные из окружения оркестратора. Минус — ещё одна зависимость и риск закоммитить .env по невнимательности. Добавьте .env в .gitignore первым же коммитом.
Сравнение подходов
| Подход | Когда брать | Цена |
|---|---|---|
| Один файл + env-переменные | Небольшой проект, различий мало | Распухает от if DEBUG при росте |
| Пакет settings | Различаются приложения, middleware, бэкенды | Сложнее анализ, риск забыть переопределить |
| django-environ + .env | Docker, много секретов, единый формат | Лишняя зависимость, риск закоммитить .env |
| Комбинация: пакет + env | Средние и крупные проекты | Больше файлов, надо документировать |
На практике крупные проекты комбинируют: пакет settings/ задаёт структуру, а различающиеся значения внутри файлов всё равно читаются из окружения.
Пять ошибок, которые повторяют чаще всего
- DEBUG=True на проде. Traceback с путями и переменными видит любой посетитель. Проверяйте:
./manage.py diffsettingsна боевом хосте или запрос к несуществующей странице — на проде должна быть страница 404, а не отладочная. - SECRET_KEY в репозитории. Даже если удалить его новым коммитом, он остаётся в истории git. Если ключ утёк — сгенерируйте новый и смените, старый считайте скомпрометированным.
- Пустой ALLOWED_HOSTS при DEBUG=False. Приложение отдаёт
Bad Request (400)на все запросы. Django требует заполнить список, когда отладка выключена. - Один SECRET_KEY на все окружения. Ключ определяет подпись сессий и CSRF-токенов. Общий ключ между dev и prod означает, что сессию, выпущенную локально, примет боевой сервер.
- Дефолтные значения секретов.
env.get("SECRET_KEY", "insecure-default")проходит все тесты и молча уезжает на прод. Пусть отсутствие обязательной переменной роняет запуск.
Как проверить, что настройки не текут
Перед выкладкой прогоните встроенную проверку безопасности:
# покажет предупреждения по DEBUG, SECRET_KEY, HTTPS-заголовкам
./manage.py check --deploy
Команда сообщит, если DEBUG включён, SECRET_KEY подозрительно короткий, не заданы заголовки безопасности. Это не панацея, но ловит самые грубые промахи. Полный список проверок безопасности смотрите в разделе deployment checklist документации Django вашей версии.
Что настроить дальше
Разделив настройки, займитесь секретами всерьёз: для одного сервера хватит .env с правами 600, для нескольких — менеджера секретов (Vault, secrets в оркестраторе). Затем добавьте ./manage.py check --deploy в CI, чтобы сборка падала при DEBUG=True. И вынесите логирование в отдельную секцию, разную для dev и prod, — это следующий по частоте источник различий после базы данных.
Создатель презентаций: Структура и текст каждого слайда: заголовки-утверждения, одна мысль на слайд, заметки докладчику.
Спросить за 150 ₽Источники: Django deployment checklist (официальная документация), Django settings reference
Частые вопросы
А если у меня Django 4.2, а не 5.x — подходы отличаются?
Что делать, если SECRET_KEY уже попал в репозиторий?
Куда класть DJANGO_SETTINGS_MODULE при подходе с пакетом settings?
Обязательно ли ставить django-environ или хватит os.environ?
Проверил check --deploy, ошибок нет — значит настройки безопасны?
Почему приложение отдаёт Bad Request (400) после выключения DEBUG?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.