Django локально поднимается одной командой — python manage.py runserver, — а публикация в интернет превращается в отдельную задачу: сервер, Python с пакетами, gunicorn, nginx перед ним, вынесенная статика, база, миграции, сертификат. Вместе это вечер работы и ещё один — на разбор белого экрана вместо сайта. На Dockbox эту часть берёт платформа: вы загружаете код, она определяет Python-проект, ставит зависимости по requirements.txt, запускает приложение и выдаёт публичную HTTPS-ссылку. Ниже — что подготовить в проекте, чтобы всё заработало с первого раза.
Почему деплой Django превращается в отдельный проект
Привычный путь — длинный чек-лист: арендовать сервер, собрать нужную версию Python, пройти pip install -r requirements.txt, запустить gunicorn, поставить перед ним nginx, отдельно отдать статику, выпустить сертификат, поднять базу и не забыть про миграции.
runserver для продакшена не годится. Это отладочный сервер без обработки внешних запросов. В боевом режиме приложение поднимает gunicorn (или uvicorn для ASGI), а перед ним встаёт nginx — иначе сайт не отдаёт HTTPS.
DEBUG=True включён по умолчанию. На ошибке Django показывает посетителю трассировку, пути к файлам и куски настроек.
Статика живёт отдельно. В продакшене CSS, скрипты и картинки отдаёт web-сервер, а не Django: не собрать их заранее — стили на опубликованном сайте исчезнут.
Порт и перезапуск. Приложение должно слушать выданный ему порт и подниматься само после падения. Платформа деплоя снимает всю эту часть и отдаёт публичную ссылку.
Шаг 1. requirements.txt в корне, venv — не в архив
requirements.txt в корне проекта — это признак, по которому платформа понимает, что перед ней Python-проект, и по которому ставит зависимости при сборке. В списке обязательны сам Django и gunicorn — сервер, который держит приложение. Типичный набор:
Django==5.0.6
gunicorn==22.0.0
psycopg[binary]==3.1.19
Если проект работает с картинками, очередями или внешними API, добавьте Pillow, celery, httpx — по факту импортов. Папку venv в архив не кладите: платформа поставит те же библиотеки сама, а лишние мегабайты замедлят загрузку.
Шаг 2. Секреты — в переменные окружения, а не в settings.py
Плохой вариант — так Django создаётся по умолчанию:
SECRET_KEY = "django-insecure-1a2b3c4d5e"
DATABASE_URL = "postgres://app:secret@db:5432/app"
Хороший вариант — значения приходят извне, а код только читает их:
import os
SECRET_KEY = os.environ["SECRET_KEY"]
DATABASE_URL = os.environ["DATABASE_URL"]
SECRET_KEY — ключ, которым Django подписывает сессии и токены CSRF: с ним можно подделать чужую сессию. Строка подключения к базе — прямой пароль от ваших данных. Код вы будете показывать или заливать в Git, поэтому секрет в коде = секрет утёк. На платформе значения задаются отдельно и подставляются переменными окружения:
SECRET_KEY=***
DEBUG=False
ALLOWED_HOSTS=example.com
DATABASE_URL=postgres://app:***@db:5432/app
В панели видны имена переменных, а значения — никому.
Шаг 3. DEBUG=False и свой домен в ALLOWED_HOSTS
Отладку выключите до публикации. DEBUG=True в продакшене показывает посетителю стек вызовов и содержимое настроек, а иногда и строку подключения к базе.
Пустой ALLOWED_HOSTS — это ошибка 400. При выключенном DEBUG Django раздаёт запросы только тем доменам, что там перечислены. Самый надёжный вариант — читать список из окружения:
ALLOWED_HOSTS = [h for h in os.environ.get("ALLOWED_HOSTS", "").split(",") if h]
На первом запуске можно временно указать ["*"], чтобы убедиться, что сайт открывается, а затем сузить список до домена. В продакшене "*" оставлять нельзя: так Django принимает запросы с любым заголовком Host.
Шаг 4. Порт — из PORT, запуск — под gunicorn
Порт не хардкодьте. Платформа передаёт нужный порт через переменную PORT, и это единственное место, куда приходит трафик. Жёстко прописанный 127.0.0.1:8000 — причина номер один, по которой приложение работает, а ссылка не открывается.
Плохой вариант:
gunicorn config.wsgi:application --bind 127.0.0.1:8000
Рабочий — слушать 0.0.0.0 и брать порт из окружения:
gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 3
Для ASGI-проекта вместо gunicorn укажите uvicorn с теми же параметрами.
Шаг 5. Статика не появится сама: STATIC_ROOT и collectstatic
Django не раздаёт статику в продакшене: CSS, скрипты и картинки отдаёт web-сервер, и собрать их нужно в одну папку. В settings.py задаётся, куда:
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
Сборка выполняется при каждом деплое, иначе новый стиль в коде не доедет до сайта:
python manage.py collectstatic --noinput
Флаг --noinput обязателен: без него команда спрашивает про перезапись файлов и ждёт ответа, которого в неинтерактивном запуске не будет. Загруженные пользователями файлы (MEDIA) так не собираются — им нужно постоянное хранилище.
Шаг 6. База, миграции и загрузка кода
Базу можно поднять рядом с приложением — сервисом в docker-compose внутри проекта.
services:
app:
build: .
db:
image: postgres:16
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Именованный том у базы обязателен. Без него данные обнуляются при первом же обновлении кода. Второй вариант — внешняя база: её строку подключения указываете в DATABASE_URL.
Миграции выполняются при старте. Терминала внутри проекта нет, поэтому их прогоняют из стартовой команды до запуска сервера:
python manage.py migrate --noinput && python manage.py collectstatic --noinput && gunicorn config.wsgi:application --bind 0.0.0.0:$PORT
Файлы пользователей — в постоянное хранилище: всё, что приложение пишет внутрь контейнера, при обновлении кода пересоздаётся.
Дальше проект попадает на платформу ZIP-архивом или импортом репозитория GitHub — приватный тоже подойдёт. Платформа сама ставит зависимости, собирает и запускает приложение, а логи сборки видны в панели в реальном времени. Собственный домен подключается в настройках проекта.
Сколько это стоит и как работают контейнеры
Администрировать сервер не нужно — контейнеры работают постоянно, без ограничения по часам и без «засыпания». Упавший контейнер платформа перезапускает сама.
Starter — 990 ₽/мес. 1 vCPU, 2 GB RAM, 20 GB SSD, один проект. Первый месяц бесплатный. Для одного Django-сайта этого достаточно, и лимита по времени работы нет.
Pro — 2 490 ₽/мес. 2 vCPU, 4 GB RAM, до пяти проектов — если рядом живут база, очередь задач и воркер.
Team — 4 990 ₽/мес — для команд, которым нужен запас ресурсов и SLA.
Тарифы и подробности — на dockbox.host.
Частые вопросы
Приложение работает, а ссылка не открывается — почему?
Почти всегда дело в порте: gunicorn слушает 127.0.0.1:8000 вместо порта из PORT и адреса 0.0.0.0. Платформа направляет трафик только на выданный ею порт, поэтому жёсткий номер — причина номер один. Вторая — ответ 400, если в ALLOWED_HOSTS нет вашего домена.
Что делать с SECRET_KEY и строкой подключения к базе?
Не хранить их в settings.py, а перенести в переменные окружения проекта — SECRET_KEY и DATABASE_URL: платформа держит их зашифрованными и подставляет при запуске, а код читает через os.environ. Если SECRET_KEY уже лежал в репозитории, считайте его утёкшим и сгенерируйте новый.
Как прогнать миграции после деплоя?
Терминала внутри проекта нет, поэтому миграции выполняются при старте: python manage.py migrate --noinput в начале стартовой команды, перед gunicorn. Так таблицы обновляются на каждом деплое, а не при первом визите пользователя.
Стили и картинки пропали на опубликованном сайте — в чём дело?
Django не раздаёт статику в продакшене: её собирают в STATIC_ROOT командой python manage.py collectstatic --noinput при каждом деплое. Локально всё видно, потому что в разработке файлы отдаёт сам Django, — этой разницей объясняются почти все «слетевшие» вёрстки.
Сайт отдаёт 500 сразу после деплоя — с чего начать?
Три частые причины: не заданы переменные окружения (SECRET_KEY, DATABASE_URL), база недоступна или миграции не прошли, либо код остался с DEBUG=True. Настоящая ошибка написана в логах запуска — откройте их в панели.