Django в Docker: Dockerfile и docker compose
Как поднять Docker-контейнер с Django-приложением: Dockerfile, docker compose с PostgreSQL, переменные окружения, миграции при старте, Gunicorn и healthcheck.
Содержание · 11 разделов
- Структура проекта
- Настройки Django из переменных окружения
- Dockerfile для Django
- Миграции и collectstatic при старте
- docker-compose.yml с PostgreSQL
- Запуск и управление
- Статика: Nginx или WhiteNoise
- Режим разработки
- Частые ошибки
- could not translate host name “db” / failed to resolve host ‘db’
- Connection refused при старте web
- password authentication failed после смены пароля
- Админка без стилей
- Изменения в коде не видны
- Частые вопросы
- Как открыть базу из контейнера в DBeaver или pgAdmin?
- Как сделать резервную копию базы из контейнера?
- Как добавить Celery и Redis в docker compose?
- Как применять миграции, если контейнеров web несколько?
- Нужен ли Docker, чтобы разрабатывать на Django?
- Итог
Чтобы поднять Docker-контейнер с Django-приложением, нужны три файла: Dockerfile описывает образ с Python, зависимостями и кодом проекта; entrypoint.sh при старте применяет миграции и собирает статику; docker-compose.yml запускает рядом PostgreSQL и связывает сервисы. После этого весь стек поднимается одной командой:
docker compose up -d --build
Ниже — готовые файлы с объяснением каждой строки: настройки Django из переменных окружения, Gunicorn вместо runserver, тома для базы и статики, healthcheck и порядок запуска. Нужен установленный Docker с плагином Compose v2 (команда docker compose, без дефиса). Проект создан командой startproject с пакетом настроек config.
Структура проекта
mysite/
├── config/
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── blog/
├── manage.py
├── requirements.txt
├── Dockerfile
├── entrypoint.sh
├── docker-compose.yml
├── .dockerignore
└── .env # секреты, не коммитится
В requirements.txt должны быть Django, сервер приложений и драйвер PostgreSQL:
Django>=5.2
gunicorn>=23
psycopg[binary]>=3.2
Настройки Django из переменных окружения
В контейнере нельзя держать секреты и параметры базы прямо в settings.py: один и тот же образ запускается локально, на тестовом и боевом сервере. Читайте их из окружения:
# config/settings.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",")
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["POSTGRES_DB"],
"USER": os.environ["POSTGRES_USER"],
"PASSWORD": os.environ["POSTGRES_PASSWORD"],
"HOST": os.environ.get("POSTGRES_HOST", "db"),
"PORT": os.environ.get("POSTGRES_PORT", "5432"),
}
}
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
HOST по умолчанию — db: так будет называться сервис PostgreSQL в compose, и Docker сам резолвит это имя в адрес контейнера. Подробно о параметрах подключения — в статье «Как подключить PostgreSQL к Django».
Сами значения положите в .env рядом с docker-compose.yml:
# .env — добавьте в .gitignore
DJANGO_SECRET_KEY=замените-на-длинную-случайную-строку
DJANGO_DEBUG=0
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,example.com
POSTGRES_DB=mysite
POSTGRES_USER=mysite
POSTGRES_PASSWORD=надёжный-пароль
Сгенерировать ключ можно так: python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())".
Dockerfile для Django
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
RUN chmod +x /app/entrypoint.sh \
&& useradd --create-home app \
&& mkdir -p /app/staticfiles /app/media \
&& chown -R app:app /app/staticfiles /app/media
USER app
EXPOSE 8000
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3", "--access-logfile", "-"]
Что здесь важно:
python:3.12-slim— официальный образ на Debian без лишних пакетов. Дляpsycopg[binary]компилятор не нужен.PYTHONUNBUFFERED=1— логи Python сразу попадают вdocker compose logs, без буферизации.- Сначала
requirements.txt, потом код. Docker кеширует слои: пока зависимости не менялись,pip installпри пересборке не выполняется, и правка кода собирается за секунды. - Пользователь
app. Процесс в контейнере не должен работать от root. Папки для статики и медиа заранее создаются и отдаются ему: туда пишетcollectstatic. - Gunicorn вместо
runserver. Встроенный сервер Django не предназначен для продакшна. Адрес0.0.0.0обязателен: на127.0.0.1Gunicorn будет недоступен снаружи контейнера. Стартовое число воркеров, которое рекомендует Gunicorn, —2 × ядра + 1.--access-logfile -выводит журнал запросов в stdout.
Чтобы в образ не попали виртуальное окружение, локальная база и секреты, создайте .dockerignore:
.venv
__pycache__
*.pyc
.git
.env
db.sqlite3
staticfiles
media
Миграции и collectstatic при старте
Применять миграции на этапе сборки образа нельзя: при docker build база недоступна и переменных окружения продакшна нет. Поэтому их выполняет скрипт, который запускается перед основной командой контейнера:
#!/bin/sh
set -e
python manage.py migrate --noinput
python manage.py collectstatic --noinput
exec "$@"
set -e останавливает контейнер, если миграция упала, — сайт не запустится с несовместимой схемой базы. exec "$@" заменяет процесс скрипта командой из CMD, то есть Gunicorn. Так он получает сигналы остановки от Docker напрямую и корректно завершает воркеры. Как устроены миграции и что делать при конфликтах, рассказано в статье «Миграции в Django».
Важно: если вы работаете в Windows, сохраните
entrypoint.shс переводами строк LF, а не CRLF. Иначе контейнер упадёт сexec /app/entrypoint.sh: no such file or directory, хотя файл на месте. В VS Code формат переключается в правом нижнем углу (CRLF → LF), для git задайте*.sh text eol=lfв.gitattributes. Чтобы скрипт оставался исполняемым и при монтировании кода в контейнер, сохраните этот бит в git:git update-index --chmod=+x entrypoint.sh.
docker-compose.yml с PostgreSQL
services:
db:
image: postgres:18
env_file: .env
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
web:
build: .
env_file: .env
ports:
- '8000:8000'
volumes:
- static_data:/app/staticfiles
- media_data:/app/media
depends_on:
db:
condition: service_healthy
healthcheck:
test:
[
'CMD',
'python',
'-c',
"import urllib.request; urllib.request.urlopen('http://localhost:8000/health/')",
]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
restart: unless-stopped
volumes:
postgres_data:
static_data:
media_data:
Разберём ключевые места.
Переменные окружения. Оба сервиса читают .env. Официальный образ postgres при первом запуске создаёт базу и пользователя из POSTGRES_DB, POSTGRES_USER и POSTGRES_PASSWORD, а Django берёт те же значения для подключения. Обратите внимание: эти переменные срабатывают, только пока том с данными пуст. Смена пароля в .env после первого запуска уже существующую базу не изменит.
Тома (volumes). Именованный том postgres_data хранит данные базы между перезапусками и пересборками — без него docker compose down уничтожил бы все записи. Точка монтирования зависит от версии образа. С postgres:18 том подключают к /var/lib/postgresql, а данные лежат во вложенной папке с номером версии (/var/lib/postgresql/18/docker). Для postgres:17 и старше том монтируют в /var/lib/postgresql/data: если указать /var/lib/postgresql, данные окажутся в анонимном томе и пропадут при пересоздании контейнера. Тома static_data и media_data нужны, чтобы статику и загруженные файлы мог раздавать отдельный веб-сервер (см. ниже), а медиафайлы не терялись при пересоздании контейнера.
Healthcheck и порядок запуска. depends_on с condition: service_healthy запускает Django только после того, как pg_isready подтвердит, что PostgreSQL принимает подключения. Простой depends_on: [db] ждёт лишь старта контейнера, и первые migrate падали бы с Connection refused. Двойной $$ экранирует $ от подстановки самим Compose — переменную раскроет оболочка внутри контейнера.
Для healthcheck сервиса web добавьте лёгкий URL, который не трогает базу:
# config/urls.py
from django.http import HttpResponse
from django.urls import path
urlpatterns = [
# ...
path("health/", lambda request: HttpResponse("ok")),
]
В slim-образе нет curl, поэтому проверка написана на Python. Запрос идёт на localhost, так что он должен быть в DJANGO_ALLOWED_HOSTS — иначе Django ответит 400, и контейнер получит статус unhealthy.
Запуск и управление
docker compose up -d --build # собрать образ и запустить в фоне
docker compose ps # статус сервисов и healthcheck
docker compose logs -f web # логи Django и Gunicorn
Откройте http://localhost:8000/. Администратора создайте внутри контейнера — подробно в статье про createsuperuser:
docker compose exec web python manage.py createsuperuser
Другие полезные команды:
docker compose exec web python manage.py shell # консоль Django
docker compose exec db psql -U mysite mysite # консоль PostgreSQL
docker compose down # остановить и удалить контейнеры
docker compose down -v # то же + удалить тома (данные БД!)
После изменения кода выполните docker compose up -d --build — Compose пересоберёт образ и пересоздаст только изменившиеся сервисы.
Статика: Nginx или WhiteNoise
Gunicorn не раздаёт статические файлы, поэтому при DEBUG = False админка откроется без стилей. Два решения:
- WhiteNoise — пакет, который отдаёт статику прямо из Django. Достаточно установить его и добавить middleware; отдельный контейнер не нужен.
- Nginx в отдельном контейнере с общим томом
static_data:
services:
# db и web — как выше
nginx:
image: nginx:stable-alpine
ports:
- '80:80'
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- static_data:/app/staticfiles:ro
- media_data:/app/media:ro
depends_on:
- web
# nginx.conf
server {
listen 80;
location /static/ { alias /app/staticfiles/; }
location /media/ { alias /app/media/; }
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
С Nginx уберите ports у сервиса web: снаружи должен быть доступен только порт 80. HTTPS, CSRF_TRUSTED_ORIGINS и чек-лист настроек продакшна разобраны в статье о деплое Django с Gunicorn и Nginx.
Режим разработки
Для локальной работы удобно монтировать код в контейнер и запускать runserver с автоперезагрузкой. Compose автоматически объединяет docker-compose.yml с файлом docker-compose.override.yml, если он лежит рядом:
# docker-compose.override.yml — только для разработки, на сервер не копируйте
services:
web:
command: python manage.py runserver 0.0.0.0:8000
environment:
DJANGO_DEBUG: '1'
volumes:
- .:/app
Частые ошибки
could not translate host name “db” / failed to resolve host ‘db’
Django запущен не в Compose (например, python manage.py runserver на хосте), и имя db ему неизвестно. Вне контейнеров используйте POSTGRES_HOST=localhost и пробросьте порт 5432 у сервиса db.
Connection refused при старте web
PostgreSQL ещё не готов. Проверьте, что у db есть healthcheck, а у web — depends_on с condition: service_healthy.
password authentication failed после смены пароля
Пароль в .env поменяли, а база в томе создана со старым. Смените пароль в самой базе (ALTER USER) или, если данные не нужны, пересоздайте том: docker compose down -v.
Админка без стилей
Не выполнен collectstatic или статику никто не раздаёт: подключите WhiteNoise или Nginx. Заодно проверьте, что STATIC_ROOT совпадает с путём тома.
Изменения в коде не видны
Без bind mount код копируется в образ при сборке. Пересоберите: docker compose up -d --build.
Частые вопросы
Как открыть базу из контейнера в DBeaver или pgAdmin?
Опубликуйте порт PostgreSQL только на локальный интерфейс, чтобы база не стала доступна из интернета:
services:
db:
ports:
- '127.0.0.1:5432:5432'
В клиенте укажите хост localhost, порт 5432 и логин с паролем из .env. На сервере вместо открытия порта удобнее SSH-туннель.
Как сделать резервную копию базы из контейнера?
Запустите pg_dump внутри контейнера и сохраните вывод на хосте; флаг -T отключает псевдотерминал, чтобы дамп не испортили служебные символы:
docker compose exec -T db pg_dump -U mysite mysite > backup.sql
docker compose exec -T db psql -U mysite mysite < backup.sql # восстановление
В Windows PowerShell 5.1 оператор > сохраняет файл в UTF-16, и psql его не прочитает. Выполните команду в cmd или PowerShell 7 либо запишите дамп внутри контейнера (pg_dump -f /tmp/backup.sql) и скопируйте его командой docker compose cp db:/tmp/backup.sql ..
Как добавить Celery и Redis в docker compose?
Redis — готовый образ, а воркер Celery собирается из того же образа, что и web, только с другой командой:
services:
redis:
image: redis:7
restart: unless-stopped
celery:
build: .
command: celery -A config worker -l INFO
env_file: .env
environment:
CELERY_BROKER_URL: redis://redis:6379/0
depends_on:
- redis
- db
Подключение Celery к проекту описано в статье «Celery в Django». Воркер наследует из образа ENTRYPOINT с миграциями и collectstatic — чтобы не выполнять их при каждом старте, задайте ему entrypoint: [].
Как применять миграции, если контейнеров web несколько?
При масштабировании за Nginx (docker compose up --scale web=3, без ports у web) каждый контейнер запустит migrate из entrypoint.sh одновременно. Надёжнее убрать миграции из скрипта и выполнять их отдельной разовой командой перед обновлением:
docker compose run --rm web python manage.py migrate
Нужен ли Docker, чтобы разрабатывать на Django?
Нет. Для учёбы и небольших проектов хватает виртуального окружения и SQLite. Docker полезен, когда локально нужны те же PostgreSQL, Redis и версии сервисов, что на сервере, или когда над проектом работает команда и окружение должно собираться одинаково у всех.
Итог
Минимальный продакшн-стек Django в Docker — это Dockerfile на python:3.12-slim с Gunicorn и непривилегированным пользователем, entrypoint.sh с migrate и collectstatic, а также docker-compose.yml с PostgreSQL, томом для данных и healthcheck. Секреты живут в .env, а не в образе. Полную схему продакшна с Nginx, HTTPS и systemd смотрите в руководстве по деплою Django, фоновые задачи добавляются отдельным сервисом — см. Celery в Django.
- #Django
- #Docker
- #docker compose
- #PostgreSQL
- #Gunicorn