Django 与 Tailwind CSS:Docker 镜像轻量化的多阶段构建策略

对于后端开发者来说,CSS 常常像永无止境的任务。曾经我们一丝不苟地写 style.css,直到遇到 Bootstrap,才一时欢呼。然而,随着维护的不断深入,越发难以在 Bootstrap 的框架内进行定制,痛苦也随之加剧。

如今的潮流无疑是 Tailwind CSS。它的 Utility‑First(工具优先)方式,只需组合类名即可完成样式,几乎是后端工程师的救星。本人也曾把项目中的 Bootstrap 全部拆除,迁移到 Tailwind,证明它已成为现代 Web 开发,尤其是 Django 项目中不可或缺的工具。

本文将分享在 Django 项目中使用 Tailwind CSS 时,针对生产环境部署的 Docker 镜像优化策略,尤其是 通过多阶段构建保持镜像轻量 的方法。


问题场景:Docker 镜像膨胀



在 Django 环境中集成 Tailwind,最常用的包是 django‑tailwind。它的安装与文档都很完善,开发环境几乎没有问题。

问题出现在 部署 阶段,尤其是 Docker 容器 环境。

在本地或裸机服务器上,可以用以下命令简单地构建 CSS:

python manage.py tailwind build

但此过程内部需要 Node.jsnpm,导致生产用的 Django Docker 镜像必须包含 Node.js。这样做在多方面都显得低效。

  • 问题 1:仅为一次性 CSS 构建就把 Node.js 运行时塞进运行镜像。
  • 问题 2:镜像体积不必要地增大。
  • 问题 3:安全角度来看,运行时包含不需要的二进制文件并不理想。

我们的目标很明确:

“在不安装 Node.js 的纯 Python 运行时镜像中,只携带已构建好的 CSS 文件”

为此,我们将使用 Docker 的多阶段构建。


解决方案:三阶段多阶段构建(Multi‑stage Build)

利用 Docker 的多阶段构建功能,可以将镜像构建过程拆分为三步:

  1. Python Builder:将 Python 包构建为 Wheel。
  2. Node Builder:使用 Node 编译 Tailwind CSS。
  3. Final Runtime:仅复制前两步的产物,生成最终运行镜像。

采用此方法,最终镜像中不包含 Node.js 或构建工具,只有运行所需的文件。


Dockerfile 示例



以下示例基于实际 Django + Tailwind 项目。路径和文件名请根据自己的项目结构(如 theme 应用、静态文件位置等)进行调整。

# -------------------------------------------------------------------
# Stage 1: Python Builder
# 安装 Linux 依赖并将 Python 包构建为 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 扫描 Django 模板/py 文件
COPY . .

WORKDIR /app/theme/static_src

# 安装依赖并构建
RUN npm ci
RUN npm run build

# 假设构建产物为:/app/theme/static/css/dist.css


# -------------------------------------------------------------------
# Stage 3: Final Runtime
# 最终运行镜像,只有 Python 环境和静态文件
# -------------------------------------------------------------------
FROM python:3.11-slim

WORKDIR /app

# 1. 安装 Python Builder 中构建的包
COPY --from=python-builder /app/wheels /wheels
COPY --from=python-builder /app/requirements.txt .
RUN pip install --no-cache-dir /wheels/*

# 2. 复制应用源码
COPY . .

# 3. 仅复制 Node Builder 中构建的 CSS
COPY --from=node-builder /app/theme/static/css/dist.css \
    /app/theme/static/css/dist.css

# 4. 运行 collectstatic
RUN python manage.py collectstatic --noinput

# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "config.wsgi:application"]

根据实际情况,还可以考虑以下优化:

  • 使用 .dockerignore 排除不必要的文件(如 .git、本地媒体文件等)。
  • 添加环境变量 PYTHONDONTWRITEBYTECODE=1PYTHONUNBUFFERED=1 等。
  • 在 Alpine 镜像中仅安装所需的系统库。

核心要点总结

1. Node Builder 阶段的职责分离

Stage 2 只负责 CSS 构建。关键点:

  • Tailwind 的 content 配置往往会扫描 Django 模板(templates/**/*.html)和 Python 代码。
  • 示例中复制了整个项目,以确保 JIT 模式能正确识别类名。
  • 若项目规模大且 content 路径明确,可仅复制构建所需的目录(如 static_src、模板目录)。此时需同步调整 DockerfileCOPY 路径和 tailwind.config.jscontent 配置。

2. 利用 npm run build 脚本

django‑tailwind 安装后生成的 package.json 通常包含类似脚本:

{
  "scripts": {
    "build": "tailwindcss -c tailwind.config.js -o ../static/css/dist.css --minify"
  }
}

多阶段构建中直接执行 npm run buildnpm run build:prod 即可。只需确认构建产物路径,然后在 Stage 3 中使用 COPY --from=node-builder 复制即可。

3. 最终镜像的轻量化与安全性

在 Stage 3 中:

  • 完全不包含 npmnodetailwindcss CLI。
  • Python 包通过 Wheel 一次性安装,提升构建速度与缓存利用。
  • 结果是:
  • 镜像体积减小。
  • 攻击面缩小。
  • 容器真正只保留运行所需的内容。

长期来看,这种差异能带来:

  • CI/CD 速度提升。
  • 镜像存储成本下降。
  • 安全审计时需要移除的组件更少。

process of build runtime docker image

结语

在构建 Docker 镜像时,很多人会倾向于一次性把所有需要的包塞进去,确保“能跑”。但在生产环境中,镜像大小、构建时间与安全性 同样重要。

使用上述 三阶段多阶段构建 策略,您可以:

  • 将 Node.js 依赖限制在构建阶段。
  • 让最终镜像保持纯粹的 Python 运行时 + 静态产物。
  • 让部署更轻量、可预测、易维护。

如果您已有正在运行的项目,建议在下一个部署管道中尝试一次。一次性搭建好结构后,后续新项目几乎可以直接复用此模式。