回到文章列表
docker-multi-stage-build-image

【Docker】Multi-Stage Build 減少Image大小

發布於 2026-04-21·9·
DevOpsDockerCI/CD

最近在架這個網站的時候,在我的Home Server建了一個CI/CD Pipeline,為了可以在CI build Docker Image時節省Image的大小,學了一下Multi-Stage build,這篇文章就來記錄一下。

Image Layer

首先最重要的概念就是,Docker的每個Image都是由多個Layer組成的,當我們在Dockerfile裡每執行一個指令(e.g.FROM, RUN, COPY...)時,都會在原有的Layer之上再建立一個新的Layer,且該指令對檔案的任何操作或是Diff都只會記錄在這個Layer中(如果是改上一個Layer就有的檔案會複製一份再改,不影響上個Layer的完整性,Copy-on-Write)。

所以說當我們要build一個新的Image時,裡面可能就會包含很多不同的階段,像是安裝套件、編譯、打包可執行檔等等,每個步驟可能都會在對應的Layer裡產生很多最終執行檔不需要的東西,例如快取、編譯工具,導致整包Image很肥大。

dockerfile
FROM node:18

WORKDIR /app

# 複製 package.json 並安裝所有依賴 (包含 devDependencies)
COPY package*.json ./
RUN npm install

# 複製所有原始碼並進行編譯
COPY . .
RUN npm run build

# 啟動伺服器 (通常是 npm start)
CMD ["npm", "start"]

# 【結果】映像檔大小:可能高達 1GB+ (包含 node_modules 和原始碼)

Multi-Stage Build

為了處理這個問題 Docker提供了多階段build的功能,我們只需要使用FROM AS就可以把每個不同階段拆開來,只傳遞該階段重要的結果(Artifact),其他用不到的東西都可以直接丟掉。

dockerfile
# --- 第一階段:編譯階段 (命名為 build-stage) ---
FROM node:18 AS build-stage

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# 執行 build 指令,產生 dist 資料夾
RUN npm run build

# --- 第二階段:部署階段 (使用 Nginx) ---
FROM nginx:stable-alpine

# 【關鍵】從 build-stage 階段只把編譯好的靜態檔案複製到 Nginx 的目錄
COPY --from=build-stage /app/dist /usr/share/nginx/html

# Nginx 預設會啟動,所以不需要額外的 CMD

# 【結果】映像檔大小:極小 (僅包含 Nginx 及其靜態資源,約幾 MB)

小細節

有時我們的指令會安裝很多東西或產生快取,這時怎麼清理會是個重點 錯誤寫法:

dockerfile
# 第一層:安裝 git 並產生了大量的暫存檔
RUN apt-get update && apt-get install -y git

# 第二層:試圖刪除暫存檔
RUN rm -rf /var/lib/apt/lists/*

由於每個指令都是單獨的Layer,所以如果把清除Cache的指令分開來執行,這樣就沒辦法清到上個指令的東西。 正確寫法:

dockerfile
FROM python:3.9

# 使用單一 RUN 指令完成所有動作
RUN apt-get update && \
    apt-get install -y git && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

除此之外,Docker對Layer也有Cache機制,只要內容不變,就不會重新build該Layer,因此Best Practice是越不容易變化的Layer或指令要寫在越前面,或是也可以用.dockerignore忽略掉像是node_modules .git之類的東西。

References

Day25 - 菜鳥們一起深入探討 Docker - Image Layer 篇