多階段構(gòu)建(Multi-stage Build)
通過 AS builder 等分離構(gòu)建階段與運行階段,只把構(gòu)建產(chǎn)物和必要配置拷貝到最終鏡像,最小化生產(chǎn)鏡像、減少構(gòu)建體積。
最終鏡像中不會包含構(gòu)建階段的源碼、開發(fā)依賴、構(gòu)建工具等,僅包含運行階段需要的產(chǎn)物與配置。
# ========== 階段一:構(gòu)建 ==========
FROM node:22-slim AS builder
WORKDIR /app
COPY . /app
RUN corepack enable pnpm && pnpm install && pnpm build
# ========== 階段二:生產(chǎn)運行 ==========
FROM nginx:latest AS production
# 從 builder 階段僅復制構(gòu)建產(chǎn)物與 Nginx 配置
COPY --from=builder /app/web-ele/dist /etc/nginx/site_avaliable/vben-admin
# 后續(xù)部署內(nèi)容
說明:
- COPY --from=builder:從指定階段復制文件到當前階段,不引入 builder 的其它層。
- 可通過
docker build --target <AS 階段名稱>可以只構(gòu)建到該階段
啟用 BuildKit 與緩存
BuildKit 是 Docker 官方下一代構(gòu)建引擎,需 Docker 18.09 及以上,并在環(huán)境中顯式啟用。
主要能力:
- 多階段任務并行:根據(jù)依賴關(guān)系將無依賴任務并行執(zhí)行,縮短總構(gòu)建時間。
-
Dockerfile 變更下的持久化緩存:通過
--mount=type=cache將目錄掛載到宿主機緩存卷,在依賴文件未變時復用緩存。 -
導出中間產(chǎn)物:通過
--output=type=local,dest=<路徑>將某階段產(chǎn)物導出到本地,便于測試或復用。 -
默認非 root:默認不以 root 運行,需時可用
--user=root。 -
新指令:常用如 --mount,以及
--network、--platform等。-
--mount=type=cache,target=<緩存目錄>:持久化緩存,多次構(gòu)建共享。 -
--mount=type=secret,id=<id>,target=<路徑>:注入構(gòu)建期密鑰,構(gòu)建結(jié)束后不留在鏡像中。 -
--mount=type=bind,source=<宿主機路徑>,target=<容器路徑>:掛載本地文件,多用于本地調(diào)試,不建議在生產(chǎn)構(gòu)建中依賴。
-
1. 在宿主機或 CI 中啟用 BuildKit
在宿主機或 CI 配置中設置環(huán)境變量即可啟用;使用 docker-compose 構(gòu)建時建議同時開啟 COMPOSE_DOCKER_CLI_BUILD。
# 例如 .gitlab-ci.yml
variables:
DOCKER_BUILDKIT: 1 # 啟用 BuildKit 引擎
COMPOSE_DOCKER_CLI_BUILD: 1 # 使用 docker-compose build 時生效(可選)
2. 將依賴目錄掛載為緩存卷
把包管理器緩存目錄掛載為 BuildKit 緩存卷,可在鎖文件未變時復用依賴,加速后續(xù)構(gòu)建。
# 語法:RUN --mount=type=cache,target=<緩存目錄> <命令>
# pnpm:緩存指定 store 目錄
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
# npm:緩存 .npm 目錄,build 時用 --cache 指定緩存路徑
# RUN --mount=type=cache,target=/root/.npm \
# npm build --cache /root/.npm
參數(shù)說明:
- type=cache:使用 BuildKit 緩存卷。
-
id:緩存卷標識,不同
id互不共享(可選,用于區(qū)分 pnpm/npm 等)。 - target:容器內(nèi)緩存目錄路徑,與包管理器實際使用的路徑一致。
為什么能優(yōu)化
- Docker 按層緩存:每條指令對應一層,前面任一層內(nèi)容變化,后續(xù)層緩存都會失效。
- 常見寫法是先
COPY . .再RUN pnpm install。項目文件一旦改動,COPY 層變化,安裝層緩存即失效。 - 使用 BuildKit +
--mount=type=cache將 pnpm 的 store(或 npm 的 cache)掛到宿主機緩存卷后,即使項目文件變化,只要鎖文件未變,仍可復用該卷中的依賴,從而加速安裝。
注意:在 GitLab CI 等共享 Runner 上,同一 Runner 上的不同流水線會共享該緩存卷。
緩存失效條件
- 使用的
target路徑或緩存卷標識(如id)發(fā)生變化。 - 當前 RUN 所依賴的上下文發(fā)生變化(例如安裝所依賴的
package.json/ lockfile 變化)。
Dockerfile 指令優(yōu)化
- 把常變指令放在后面。前面盡量只放不常變的步驟(如先只復制依賴描述文件 → 執(zhí)行安裝 → 再復制源碼),提高層緩存命中率。
-
COPY 時避免
COPY . .。盡量只復制當前步驟需要的文件或目錄,減少無關(guān)變更導致緩存失效。 -
合并 RUN。用
&&將多條命令寫在一個 RUN 中,減少層數(shù),便于維護與緩存。 - 安裝后清理。安裝系統(tǒng)依賴后刪除 apt 緩存、臨時文件等,減小鏡像體積。
.dockerignore
通過 .dockerignore 排除不需要參與構(gòu)建的文件(如 node_modules、日志、編輯器配置等),可加快構(gòu)建并避免把敏感或無關(guān)文件 COPY 進鏡像。
執(zhí)行時機:在 docker build 時,Docker CLI 會先讀取構(gòu)建上下文目錄下的 .dockerignore,過濾掉匹配的文件,再把剩余文件列表發(fā)給 Docker Daemon 參與構(gòu)建;過濾發(fā)生在發(fā)送上下文之前,而不是在容器內(nèi)執(zhí)行時再過濾。
Docker CLI:接收并解析用戶輸入命令的客戶端。
Docker Daemon:在宿主機上常駐的守護進程,負責實際構(gòu)建與運行。
輕量鏡像選擇
優(yōu)先選用輕量基礎鏡像,例如 node:-slim、nginx:alpine* 等,只保留運行所需的最小依賴,可減小最終鏡像體積并降低攻擊面。
例如 Google 的 distroless 系列鏡像僅包含應用運行時依賴,不含 shell、包管理器等,依賴越少,潛在漏洞面越小。