Django-приложение запускается локально через python manage.py runserver. Эта команда — для разработки: её собственная документация прямо предупреждает, что встроенный сервер не предназначен для продакшена. В проде Django запускают по-другому, и у него есть несколько специфичных мест, на которых спотыкаются почти все. Разберём, как правильно запустить Django, что настроить и куда задеплоить.
WSGI- или ASGI-сервер
Django по умолчанию — WSGI-приложение, и в проде его запускает WSGI-сервер. Стандартный выбор — gunicorn:
gunicorn myproject.wsgi:application --bind 127.0.0.1:8000 --workers 4
--bind 127.0.0.1 — приложение слушает только localhost, наружу его отдаёт reverse proxy с SSL (как это настроить — в статье про nginx и Let's Encrypt). Число воркеров — ориентир 2 × ядра + 1.
Если в проекте есть асинхронные части или Django Channels (WebSocket), нужен ASGI-сервер — uvicorn или daphne с точкой входа myproject.asgi:application.
Три настройки, без которых прод не работает
Это место, где спотыкается большинство. Перед деплоем в settings обязательно:
DEBUG = False. СDEBUG = Trueв проде Django показывает полную трассировку с переменными окружения и SQL при любой ошибке — это утечка данных и дыра в безопасности. Плюс приDEBUG = TrueDjango сам отдаёт статику, маскируя её неверную настройку.ALLOWED_HOSTS. ПриDEBUG = FalseDjango отвечает400 Bad Requestна любой запрос, чейHostне перечислен вALLOWED_HOSTS. Сюда добавляют домен приложения.SECRET_KEYиз переменной окружения, а не из кода. По нему подписываются сессии и токены — в репозитории его быть не должно.
import os
DEBUG = False
ALLOWED_HOSTS = ["example.com", "www.example.com"]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
Статика: collectstatic
Django в продакшене не раздаёт статику сам. Команда collectstatic собирает все статические файлы (CSS, JS, админка) в один каталог STATIC_ROOT, откуда их отдаёт reverse proxy или middleware:
python manage.py collectstatic --noinput
Дальше два пути: либо nginx отдаёт STATIC_ROOT напрямую, либо в проект добавляют WhiteNoise — middleware, которое раздаёт статику силами самого Python-процесса. Для небольших проектов WhiteNoise проще: не нужно настраивать nginx под статику. Пропущенный collectstatic — самая частая причина «сайт работает, но без стилей».
Миграции и база
Схему базы накатывают миграциями, а не вручную, и делают это при каждом деплое — до старта приложения:
python manage.py migrate --noinput
Строку подключения к базе удобно держать в одной переменной окружения DATABASE_URL и разбирать пакетом dj-database-url. Подробнее о хранении таких переменных — в статье про секреты и .env.
За reverse proxy: HTTPS и CSRF
Когда Django стоит за reverse proxy, который терминирует TLS, само приложение получает запрос по HTTP и без подсказки считает соединение незащищённым. Из-за этого ломаются редиректы и CSRF-проверка форм. Лечится двумя настройками:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
CSRF_TRUSTED_ORIGINS = ["https://example.com"]
CSRF_TRUSTED_ORIGINS (Django 4 и новее) обязателен, иначе POST-формы и админка будут отбивать запросы с ошибкой CSRF.
Куда размещать
VPS (Beget, Timeweb, Hetzner). Полный контроль, но Python, gunicorn под supervisor, база, бэкапы, nginx, SSL и collectstatic при каждом релизе настраиваются вручную.
Managed-платформа (PaaS). Деплой по git push: платформа собирает проект, держит процесс живым, рядом managed-база, домен и SSL — автоматически. Миграции и collectstatic вешаются на стадию сборки.
Serverless (облачные функции). Django — классическое долгоживущее приложение с постоянным подключением к базе; serverless с его cold start и лимитами подходит плохо.
Деплой на VPS
git clone https://github.com/user/myproject.git /opt/myproject
cd /opt/myproject
python -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python manage.py migrate --noinput
.venv/bin/python manage.py collectstatic --noinput
Запускать gunicorn руками нельзя — процесс умрёт с закрытием сессии. Нужен супервизор с автозапуском; минимальный systemd-сервис:
[Service]
WorkingDirectory=/opt/myproject
ExecStart=/opt/myproject/.venv/bin/gunicorn myproject.wsgi:application --bind 127.0.0.1:8000 --workers 4
EnvironmentFile=/opt/myproject/.env
Restart=always
Полная настройка supervisor — в статье как держать процесс 24/7.
Деплой на managed-платформе
Подключить репозиторий, задать переменные окружения (DJANGO_SECRET_KEY, DATABASE_URL, ALLOWED_HOSTS) в панели, повесить migrate и collectstatic на стадию сборки и сделать git push. Платформа определит Python-проект, установит зависимости, запустит gunicorn и будет держать процесс живым; managed Postgres подключается через DATABASE_URL, домен и SSL поднимаются автоматически.
Частые грабли
DEBUG = Trueв проде. Любая ошибка показывает внутренности приложения постороннему.- Пустой
ALLOWED_HOSTS. Сайт отвечает400на все запросы. - Забытый
collectstatic. Страницы открываются без стилей, админка без оформления. - Статика 404.
collectstaticсделан, но nginx илиWhiteNoiseне настроены на раздачуSTATIC_ROOT. - CSRF за прокси. Формы и админка отбивают POST без
CSRF_TRUSTED_ORIGINSиSECURE_PROXY_SSL_HEADER. - Забытый
migrate. Код задеплоен, схема старая — приложение падает на первом обращении к новой таблице.
Чеклист
- Прод запускается через
gunicorn(или ASGI-сервер для Channels), безrunserver. DEBUG = False, домен вALLOWED_HOSTS,SECRET_KEYиз окружения.collectstaticвыполняется при деплое, статика раздаётся nginx илиWhiteNoise.migrateвыполняется при деплое, до старта приложения.- За reverse proxy заданы
SECURE_PROXY_SSL_HEADERиCSRF_TRUSTED_ORIGINS. - Процесс под супервизором с автоперезапуском, настроены бэкапы базы.
Managed-платформа закрывает большую часть этого списка по умолчанию: процесс держится сам, reverse proxy с SSL и managed Postgres — из коробки, а migrate и collectstatic вешаются на деплой. На Hostim Django-приложение разворачивается по git push, стек определяется автоматически, а база подключается через DATABASE_URL без ручной настройки сервера.