Docker 映像檔優化:Multi-stage Builds
如果你按照基本的 Dockerfile 寫法,建置出來的映像檔往往體積極大。例如一個簡單的 Go 語言程式,映像檔可能高達 800MB,但程式本身只有 10MB。
這是因為映像檔裡包含了編譯器、SDK、原始碼以及各種中間產物。在生產環境中,這些都是多餘且具備安全風險的。Multi-stage Builds (多階段建置) 就是解決這個問題的神器。
什麼是多階段建置?
多階段建置允許你在一個 Dockerfile 中使用多個 FROM 敘述。
每個 FROM 都可以使用不同的基礎映像檔,並開始一個新的階段。你可以從前一個階段中,只把「編譯完成的執行檔」複製到目前的階段,其餘的垃圾(如 SDK、中間檔案)都會被丟棄。
核心語法:AS 與 --from
要實作多階段建置,最關鍵的就是掌握這兩個關鍵字:
AS:為階段命名
使用 AS 可以為當前階段取一個語意化的別名,方便後續引用。
# 將當前階段命名為 "builder"
FROM node:18-alpine AS builder
--from:跨階段複製檔案
在後續的階段中,我們使用 COPY --from=<來源> 來指定要從哪個階段複製檔案。
- 使用名稱引用(推薦):引用先前定義的
AS別名。 - 使用索引引用:如果你沒取名,可以用數字代替(如
--from=0代表第一個FROM階段),但這會讓代碼難以維護。 - 從外部映像檔引用:你甚至可以直接從另一個 Docker 映像檔中複製檔案,例如
COPY --from=nginx:latest /etc/nginx/nginx.conf /etc/nginx/。
# 從名為 builder 的階段複製 /app/dist 目錄到當前階段
COPY --from=builder /app/dist /usr/share/nginx/html
實戰範例:Node.js 應用程式
傳統上,為了跑起一個 React 或 Node.js 專案,我們會把所有 node_modules 都打包進去。使用多階段建置後:
# 第一階段:建置階段 (build stage)
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 第二階段:執行階段 (run stage)
# 我們選用極小的 Nginx 映像檔作為最終成品
FROM nginx:stable-alpine
WORKDIR /usr/share/nginx/html
# 關鍵:從 builder 階段複製編譯好的產物
COPY --from=builder /app/dist .
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
差別在哪裡?
- 傳統寫法:最終映像檔包含
node:18(約 200MB+) +node_modules(約 300MB+) + 原始碼。 - 多階段建置:最終映像檔只包含
nginx:alpine(約 20MB) +dist靜態檔案 (約 2MB)。體積縮小了 20 倍以上!
進階應用:從多個來源階段複製
在複雜的應用中,你可能同時需要前端與後端的產物,多階段建置可以讓你同時處理:
# 階段一:前端 React 建置
FROM node:18-alpine AS frontend
WORKDIR /web
COPY web/package*.json ./
RUN npm install
COPY web/ .
RUN npm run build
# 階段二:後端 Go 編譯
FROM golang:1.21-alpine AS backend
WORKDIR /src
COPY server/ .
RUN go build -o app .
# 最終階段:打包成一個映像檔
FROM alpine:latest
WORKDIR /root/
# 同時從兩個不同的階段複製產物
COPY --from=frontend /web/dist ./public
COPY --from=backend /src/app .
EXPOSE 8080
CMD ["./app"]
多階段建置的優點
使用多階段建置能帶來顯著的好處:
- 映像檔極小化:省下大量雲端存儲空間與傳輸流量,部署速度大幅提升。
- 更具安全防護:最終映像檔不含編譯工具或原始碼,縮小了駭客可利用的攻擊面 (Attack Surface)。
- 簡化 CI/CD 流程:建置邏輯全部封裝在 Dockerfile 中,CI 環境無需預先安裝各種程式語言的 SDK。
- 結構清晰:建置過程與執行環境分離,一份 Dockerfile 就能看清整個交付流程。
進階技巧:針對特定階段進行建置
有時候我們只想執行測試階段:
# 只建置到名為 "builder" 的階段為止
docker build --target builder -t my-app-test .
總結
多階段建置不再只是「進階技巧」,它是目前產業中的標準規範。無論你使用哪種語言,都應該試著將建置環境與執行環境分離,這能同時換取更小的映像檔體積與更高的安全性。