Django и Tailwind CSS: стратегия многослойной сборки для легкого Docker‑образа
Для бэкенд‑разработчика CSS часто становится вечной задачей. Когда мы писали style.css вручную, а потом открыли Bootstrap, мы воскликнули от радости. Но с каждым обновлением и кастомизацией Bootstrap становился всё более болезненным.
Сейчас в моде Tailwind CSS. Утилитарный подход, где стили задаются только классами, почти спасает бэкенд‑инженеров. Я сам полностью убрал Bootstrap из проекта и перешёл на Tailwind, и теперь считаю его обязательным инструментом для современных веб‑разработок, особенно в Django.
В этой статье я расскажу, как в Django‑проекте использовать Tailwind CSS и одновременно оптимизировать Docker‑образ для продакшн‑развертывания, особенно как сделать его лёгким с помощью многослойной сборки.
Проблема: рост размера Docker‑образа
Для интеграции Tailwind в Django чаще всего используют пакет django‑tailwind. Он хорошо документирован и легко устанавливается, поэтому в локальной среде проблем нет.
Проблема возникает на этапе развертывания, особенно в Docker‑контейнере.
В локальной среде или на bare‑metal сервере можно просто собрать CSS:
python manage.py tailwind build
Но этот процесс требует Node.js и npm. Поэтому в продакшн‑образе Django приходится ставить Node.js, что неэффективно.
- Проблема 1: Для однократной сборки CSS в runtime‑образе нужно включать Node.js.
- Проблема 2: Размер образа растёт ненужно.
- Проблема 3: Включение лишних бинарных файлов ухудшает безопасность.
Наша цель проста.
"В чистый Python‑runtime без Node.js, только собранные CSS‑файлы"
Для этого используем многослойную сборку Docker.
Решение: 3‑ступенчатая многослойная сборка
Многослойная сборка позволяет разделить процесс на три этапа:
- Python Builder: сборка Python‑пакетов в Wheel.
- Node Builder: компиляция Tailwind CSS (используем npm).
- Final Runtime: копируем только нужные файлы в финальный образ.
Таким образом финальный образ содержит только то, что нужно для запуска, без Node.js и инструментов сборки.
Пример Dockerfile
Ниже пример Dockerfile для Django + Tailwind. Путь и имена файлов можно подстроить под ваш проект (например, приложение theme, расположение статических файлов и т.д.).
# -------------------------------------------------------------------
# Stage 1: Python Builder
# Устанавливаем зависимости и собираем пакеты в Wheel
# -------------------------------------------------------------------
FROM python:3.11-slim as python-builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --upgrade pip && \
pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt
# -------------------------------------------------------------------
# Stage 2: Node Builder
# Используем Node‑образ для компиляции Tailwind CSS
# -------------------------------------------------------------------
FROM node:20-alpine as node-builder
WORKDIR /app
# Копируем весь проект, чтобы Tailwind JIT мог сканировать шаблоны и Python‑файлы
COPY . .
WORKDIR /app/theme/static_src
# Устанавливаем зависимости и запускаем сборку
RUN npm ci
RUN npm run build
# Предположим, что результат находится в /app/theme/static/css/dist.css
# -------------------------------------------------------------------
# Stage 3: Final Runtime
# Финальный образ без Node.js, только Python и статические файлы
# -------------------------------------------------------------------
FROM python:3.11-slim
WORKDIR /app
# 1. Устанавливаем пакеты из Stage 1
COPY --from=python-builder /app/wheels /wheels
COPY --from=python-builder /app/requirements.txt .
RUN pip install --no-cache-dir /wheels/*
# 2. Копируем исходный код
COPY . .
# 3. Копируем только собранный CSS из Stage 2
COPY --from=node-builder /app/theme/static/css/dist.css \
/app/theme/static/css/dist.css
# 4. Собираем статические файлы Django
RUN python manage.py collectstatic --noinput
# Запуск
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "config.wsgi:application"]
Дополнительные оптимизации:
- Используйте
.dockerignore, чтобы исключить лишние файлы (например,.git, локальные медиа). - Добавьте переменные окружения
PYTHONDONTWRITEBYTECODE=1,PYTHONUNBUFFERED=1. - При использовании Alpine‑образов устанавливайте только необходимые системные библиотеки.
Ключевые моменты
1. Разделение ролей в Node‑builder
В Stage 2 мы занимаемся только сборкой CSS. Важно, чтобы Tailwind мог видеть все шаблоны и Python‑файлы, поэтому в примере копируем весь проект. Если проект большой и content Tailwind ограничен, можно копировать только нужные каталоги (static_src, шаблоны и т.д.) и соответствующим образом настроить tailwind.config.js.
2. Скрипт npm run build
В package.json, созданном django‑tailwind, обычно есть скрипт:
{
"scripts": {
"build": "tailwindcss -c tailwind.config.js -o ../static/css/dist.css --minify"
}
}
В многослойной сборке вы можете просто вызвать npm run build или npm run build:prod. Главное знать, где находится результат, чтобы корректно скопировать его в Stage 3.
3. Легкость и безопасность финального образа
В Stage 3 отсутствуют npm, node, tailwindcss CLI и другие инструменты сборки. В итоге:
- Размер образа уменьшается.
- Уменьшается поверхность атаки.
- Контейнер содержит только то, что нужно для выполнения.
Это приводит к более быстрым CI/CD‑пайплайнам, экономии места в реестре и упрощённой проверке безопасности.

Итоги
При сборке Docker‑образов часто хочется просто «сделать всё, чтобы работало». Но в продакшн‑окружении важны размер, время сборки и безопасность. Используя описанную 3‑ступенчатую стратегию, вы:
- Ограничиваете зависимость от Node.js только до этапа сборки.
- Оставляете в финальном образе чистый Python‑runtime и собранные статические файлы.
- Делаете развертывание более лёгким, предсказуемым и безопасным.
Если у вас уже есть работающий проект, попробуйте применить эту схему в следующем пайплайне. После того как вы разберётесь с настройками, вы сможете быстро использовать её и в новых проектах.
Комментариев нет.