Dockerfile, docker-compose, Gunicorn, Nginx, CI/CD, production settings
Статус: актуально для Django 5.2/6.0 и Python 3.12+. Конкретные patch-версии base image, server и системных библиотек нужно регулярно обновлять и фиксировать lock-файлом/digest.
Контейнер — способ доставить один и тот же собранный артефакт в staging и production. Он не делает приложение автоматически stateless, безопасным или отказоустойчивым. Эти свойства появляются из архитектуры и процесса deployment.
Хороший application container:
База, пользовательские файлы, cache и broker живут во внешних сервисах или управляемых volumes с отдельной политикой backup. «Вынесли из контейнера» ещё не означает «можно восстановить» — restore нужно проверять.
Internet -> CDN/LB/Ingress -> WSGI или ASGI server -> Django
| |
workers PostgreSQL
Redis
storageNginx может завершать TLS, ограничивать тело запроса, буферизовать slow clients и раздавать static. В managed platform эти задачи часто выполняют load balancer, ingress и CDN; отдельный Nginx-контейнер не является обязательным признаком production.
Для синхронного приложения часто используют Gunicorn с WSGI. Для полностью async stack — Uvicorn/Daphne/Hypercorn или Gunicorn с актуальным ASGI worker. Выбор server и worker class должен соответствовать уроку про async.
Стандартный runserver — development server. Он многопоточный по умолчанию, но не проходил production security/performance review и прямо не предназначен для deployment. Причина отказа от него не в мифе «он всегда один поток».
Ниже учебный каркас, а не универсальный готовый образ. Системные runtime libraries зависят от используемых wheels, Pillow, database driver и других пакетов.
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS builder
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:${PATH}"
WORKDIR /build
COPY requirements.lock ./
RUN python -m pip install --require-hashes -r requirements.lock
FROM python:3.12-slim AS runtime
ENV PATH="/opt/venv/bin:${PATH}" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
RUN groupadd --gid 10001 app \
&& useradd --uid 10001 --gid app --no-create-home app
WORKDIR /app
COPY /opt/venv /opt/venv
COPY . /app
USER 10001:10001
EXPOSE 8000
CMD ["gunicorn", "config.wsgi:application", "--bind=0.0.0.0:8000", "--access-logfile=-", "--error-logfile=-"]Multi-stage build удаляет compiler/build headers из runtime image, но не гарантирует маленький или безопасный образ. Просмотрите слои, CVE scan и фактически импортируемые runtime libraries.
В реальном проекте:
3.Если dependency build требует compiler/system headers, установите их только в builder. Нужные shared libraries всё равно должны присутствовать в runtime stage.
.git
.env
.venv
__pycache__
*.pyc
db.sqlite3
media
.pytest_cache
node_modules.dockerignore уменьшает context и не даёт случайно скопировать локальные credentials. Но файл уже отправленный builder или записанный в слой не становится секретным после последующего RUN rm.
Не передавайте секрет через ARG/ENV Dockerfile и не встраивайте production .env. Если приватный registry нужен во время build, используйте BuildKit secret/SSH mount так, чтобы credential не попал в слой. Runtime secrets получает deployment platform: secret manager, mounted secret file или защищённое окружение.
Build logs и provenance тоже могут раскрыть аргументы. Не выводите credentials диагностическими командами.
collectstatic собирает static всех apps в STATIC_ROOT; при ManifestStaticFilesStorage создаёт content-hashed names. Запускать его можно при build, если для build settings не нужны production secrets и database.
Распространённые стратегии:
Named volume между Nginx и web не заполняется магически содержимым image. Если каждый web container запускает collectstatic в общий volume, появляются гонки и mutable release artifact.
Пользовательские media не относятся к collectstatic. Их хранят отдельно; application replicas не должны полагаться на локальный /app/media.
Современная настройка:
STORAGES = {
"default": {
"BACKEND": "myproject.storage.MediaStorage",
},
"staticfiles": {
"BACKEND": (
"django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
),
},
}После сборки проверьте, что HTML и image действительно ссылаются на существующие manifest assets. CDN rollout должен учитывать, что старые replicas ещё возвращают старые имена.
Миграция не выполняется при docker build: сборка не должна зависеть от конкретной production database. Также не запускайте migrate в entrypoint каждой web replica. Несколько replicas начнут один DDL одновременно, а startup приложения будет зависеть от долгой блокировки schema.
Типовая последовательность:
build once -> tests/scan -> push immutable image
-> one-off migration job
-> rolling/blue-green application rollout
-> post-deploy checksДля zero-downtime миграции должны быть совместимы с прежним и новым кодом. Используйте expand/contract:
Переименование или NOT NULL на большой таблице может получить долгую блокировку. Изучите SQL через sqlmigrate, особенности вашей СУБД и план rollback. Откат application image после несовместимой schema migration может быть уже невозможен.
Если migration job успешна, а rollout провалился, возвращайте только код, совместимый с расширенной схемой. Не применяйте обратную destructive migration автоматически.
Один /health/ редко отвечает на все вопросы.
Liveness должна быть дешёвой и не падать из-за краткого сбоя необязательного upstream, иначе platform перезапустит все здоровые processes. Readiness может проверить критическую database/cache зависимость с коротким timeout, но не должна изменять данные, раскрывать версии/секреты или выполнять тяжёлый запрос.
Health endpoint не заменяет миграционный контроль. Если старый код несовместим со schema, лучше остановить rollout раньше.
services:
web:
build:
context: .
target: runtime
env_file: .env
command: gunicorn config.wsgi:application --bind=0.0.0.0:8000
depends_on:
db:
condition: service_healthy
init: true
db:
image: postgres:17
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: local-only-password
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
volumes:
pgdata:Это local example: пароль не годится для production, порт базы не публикуется наружу. depends_on с service_healthy помогает startup order в Compose, но не делает приложение устойчивым к перезапуску базы через час. Django и workers должны иметь ограниченные retries/backoff и корректно восстанавливать connections.
Compose может применяться на одном production host, если команда сознательно принимает ограничения. Для нескольких hosts нужны platform primitives для rollout, placement, secrets, readiness, autoscaling и recovery.
DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
ALLOWED_HOSTS = ["app.example.com"]
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = TrueSECURE_PROXY_SSL_HEADER добавляют только если контролируемый proxy удаляет пользовательский заголовок и сам выставляет протокол:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")Проверьте, что прямой доступ к application container закрыт, proxy задаёт Host/protocol однозначно, а body/header/timeouts ограничены до Django. HSTS вводите постепенно, как описано в security-уроке.
При rolling deployment platform отправляет SIGTERM, убирает replica из балансировки и ждёт grace period. Web server должен перестать принимать новые запросы и дать текущим завершиться. Timeout load balancer, server graceful timeout и platform termination grace согласуют между собой.
PID 1 в container обязан получать signals и собирать дочерние процессы. Exec-form CMD и init: true в Compose помогают. Shell entrypoint в конце должен использовать exec "$@", иначе signal может остаться у shell.
Длинную работу не держите в HTTP request. Celery worker при shutdown требует отдельной настройки: soft shutdown, task acknowledgment и visibility timeout связаны с delivery semantics.
Надёжный pipeline обычно выполняет:
Не пересобирайте «тот же release» отдельно для production: получите другой набор пакетов. Тег latest можно оставить удобным указателем, но deployment должен знать digest/SHA.
CI credentials получают минимальные permissions и короткий срок жизни, предпочтительно через workload identity/OIDC. SSH-команда из одного YAML-примера не является полноценной безопасной delivery system.
Blue-green или rolling rollout — только механизм переключения. Нужны также:
«Быстро перезапустить один server» не является zero-downtime. Blue-green без совместимой базы тоже не гарантирует откат.
.dockerignore и BuildKit secrets не пропускают credentials в layers;manage.py check --deploy запускается с production settings.python:3.11-slim из прежнего урока заменены целевой линией Python 3.12+.runserver удалён из production-сценария, но причина сформулирована корректно: это dev server, а не обязательно «один поток».migrate в entrypoint каждой replica признан опасным; используется one-off release job.Далее: Мониторинг и Логирование