3.3 Dockerfile 極簡優化技巧#
為什麼要優化 Dockerfile?#
兩個動機:
- 快:每次改程式碼都要重新 build。指令順序安排得好,改一行程式碼只 rebuild 一小部分(幾秒);安排得不好,每次都裝全部依賴(幾分鐘)。
- 小:Image 越小,下載、部署、啟動越快,安全漏洞面也越小。
核心觀念只有一個:理解 Docker 的層快取機制,一切優化都從它出發。
快取機制:一層一層比對#
Docker 建構時,會從頭逐條指令比對快取:
- 指令內容沒變、且之前的層都沒變 → 直接重用快取層
- 某一層失效了 → 它之後的所有層全部失效,重新執行
flowchart TD
A[FROM python:3.12-slim] --> B[COPY requirements.txt .]
B --> C[RUN pip install]
C --> D[COPY . .]
D --> E[CMD]
B -.->|requirements.txt 沒變| B2[✓ 重用快取]
D -.->|app.py 改了| D2[✗ 這層起全部失效]技巧一:把最不常變的放前面#
Dockerfile 的指令順序,應該按「變動頻率由低到高」排列:
# 好:依賴檔案先複製、先安裝(最不常變)
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . . # 程式碼(常變)放最後
CMD ["python", "app.py"]
# 壞:程式碼先複製,改任何一行都要重裝全部依賴
FROM python:3.12-slim
WORKDIR /app
COPY . . # ← 改一行 app.py,這層就失效
RUN pip install -r requirements.txt # ← 於是每次都重跑,幾分鐘
CMD ["python", "app.py"]
這是所有 Dockerfile 優化中投資報酬率最高的一招。
技巧二:.dockerignore 排除不必要的檔案#
建構上下文(docker build . 的那個點)會把目錄內所有檔案交給 Docker——包括 .git、node_modules、日誌、暫存檔。在 Dockerfile 同目錄建立 .dockerignore(語法與 .gitignore 相同):
.git
.gitignore
node_modules
__pycache__
*.log
.env
Dockerfile
.dockerignore
好處:建構更快、Image 更小,而且避免把機密(.env)打包進 Image——這是安全底線。
技巧三:合併 RUN、清理快取#
每條 RUN 產生一層,層內的檔案刪了也不會讓 Image 變小(刪除動作只是記在新層)。所以要在同一條 RUN 內完成「安裝 + 清理」:
# 壞:apt 快取留在層裡
RUN apt-get update && apt-get install -y curl
# 好:同一層內清理
RUN apt-get update && apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
技巧四:選小的基礎 Image#
python:3.12(約 1GB)→python:3.12-slim(約 150MB)→python:3.12-alpine(約 50MB)- 編譯型語言(Go、Rust)可用多階段建構(見 3.1 節 Go 範例):編譯層用大鏡像,最終層只放執行檔
檢驗成果#
docker build -t my-app:v2 .
docker images my-app # 比較 v1 與 v2 的 SIZE
再改一行程式碼重新 build,觀察 log 中 CACHED 字樣的數量——快取命中越多,優化越成功。
本章小結#
- 一切優化都源於層快取機制:某層失效,之後全部失效
- 指令按變動頻率由低到高排列(依賴先、程式碼後)——報酬率最高的一招
.dockerignore排除.git、node_modules、.env(兼顧速度與安全)- 合併 RUN、同層清理,刪除不會縮小已成的層
- 選
-slim/alpine底層,編譯型語言用多階段建構
想一想#
- 為什麼「先 COPY 程式碼、再 RUN pip install」會讓每次 build 都重裝依賴?
- 在某一層
RUN rm -rf /big-file,Image 會變小嗎?為什麼? - 為什麼
.env一定要放進.dockerignore?如果被打包進 Image 會有什麼風險?