Django 與 Tailwind CSS:Docker 映像精簡的多階段建構策略
對於後端開發者來說,CSS 常像永恆的課題。從一行行手寫 style.css 的日子,到遇見 Bootstrap 時的歡呼,隨著維護次數增加,越來越難以在 Bootstrap 的框架之外進行自訂化,痛苦不斷。
近年來的主流無疑是 Tailwind CSS。只需組合類別即可完成樣式的「工具優先」方式,對後端工程師而言幾乎是救星。本人亦曾將舊專案的 Bootstrap 全部拆除,改用 Tailwind 進行遷移,證實此工具已成為現代 Web 開發,尤其 Django 專案中不可或缺的一環。
本文將分享在 Django 專案中使用 Tailwind CSS 時,為生產環境部署優化 Docker 映像,尤其是 利用多階段建構保持映像輕量化 的方法。
問題情境:Docker 映像膨脹
在 Django 環境中整合 Tailwind,最常使用的套件是 django-tailwind。安裝與文件化都相當完善,於本機開發環境中不會遇到大問題。
問題出現在 部署 階段,尤其是 Docker 容器 環境。
在本機或裸金屬伺服器上,可簡單執行以下指令建構 CSS:
python manage.py tailwind build
但此過程內部需要 Node.js 與 npm。因此,為了在生產用 Django Docker 映像中包含 Node.js,造成多重低效。
- 問題 1: 只為一次性 CSS 建構(實際上只需執行一次)而將 Node.js 依賴加入執行階段映像。
- 問題 2: 映像尺寸不必要地膨脹。
- 問題 3: 從安全角度看,執行時不需要的二進位檔包含在內並非好事。
我們的目標很明確:
「在純 Python 執行階段映像中,僅帶入已建構好的 CSS 檔案」
為此,我們將利用 Docker 的多階段建構。
解決方案:三階段多階段建構 (Multi-stage Build)
透過 Docker 的多階段建構功能,可將上述問題乾淨地拆解。建構流程分為三個階段:
- Python Builder: 將 Python 套件以 Wheel 形式建構。
- Node Builder: 使用 Node 來編譯 Tailwind CSS(使用 npm)。
- Final Runtime: 只複製前兩階段的成果,產生最終執行映像。
採用此方式,最終映像中不會包含 Node.js 或任何建構工具,僅保留執行所需的檔案。
Dockerfile 範例
以下為以實際 Django + Tailwind 專案為基礎的 Dockerfile 範例。路徑與檔名請依各自專案結構(如 theme 應用名稱、靜態檔位置等)調整。
# -------------------------------------------------------------------
# Stage 1: Python Builder
# 安裝 Linux 依賴並以 Wheel 形式建構 Python 套件。
# -------------------------------------------------------------------
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
# 安裝依賴並建構
# 依據 package.json 中的 build 指令執行(通常為 tailwind css 建構命令)
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. 從 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 檔
# 依照 django-tailwind 設定,確定建構檔位置。
# 此處假設為 theme/static/css/dist.css。
COPY --from=node-builder /app/theme/static/css/dist.css \
/app/theme/static/css/dist.css
# 4. 執行 collectstatic
# Django 會將 Node 建構結果(css)納入最終靜態檔。
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 的
content設定常會掃描 Django 範本 (templates/**/*.html) 或 Python 程式碼。 - 範例中為確保 JIT 模式能正確偵測類別,將整個專案複製進來。
若專案極大且 content 路徑明確,可僅複製建構所需的路徑(如 static_src、範本目錄),但需同步調整 Dockerfile 的 COPY 路徑與 tailwind.config.js 的 content 設定。
2. npm run build 指令的使用
安裝 django-tailwind 時,package.json 會自動產生類似以下腳本:
{
"scripts": {
"build": "tailwindcss -c tailwind.config.js -o ../static/css/dist.css --minify"
}
}
多階段建構中可直接執行 npm run build 或 npm run build:prod,只要確定建構結果的路徑即可。
3. 最終映像的輕量化與安全性
在 Stage 3 中:
- 完全不包含
npm、node、tailwindcssCLI。 - Python 套件以 Wheel 方式一次安裝,提升建構速度與快取效益。
- 結果:
- 映像尺寸縮減。
- 攻擊面降低。
- 容器「僅擁有執行所需」的狀態。
長期而言,這些微小差異可帶來:
- CI/CD 速度提升。
- Registry 儲存成本下降。
- 安全審核時需移除的項目減少。

結語
在建構 Docker 映像時,常會因「先讓它能跑」的心態,將所有必要套件塞進同一映像。然而在生產環境,映像大小、建構時間與安全性 都是關鍵。
結合 Django 與 Tailwind 時,採用上述 三階段多階段建構策略:
- 將 Node.js 依賴限制於建構階段。
- 最終映像僅保留純 Python 執行環境與靜態結果。
此模式使部署環境更輕量、可預測且易於維護。若已有運營中的專案,建議於下一次部署管道中立即試用。一次架構完成後,未來新專案亦可直接套用此模式。
目前沒有評論。