多階段建置把建置工具擋在最終 image 之外

編譯器、devDependencies、原始碼在執行時完全用不到,卻全都留在最終 image 裡:又肥(下載慢、部署慢、存放成本高)又不安全(多餘的工具=多餘的攻擊面,原始碼也被打包進去)。教材稱這是實務上最有價值的單一技巧,而大部分入門教學不會講。

做法:第一階段編譯,第二階段只拿產出物

FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci                       # 裝全部依賴(含 dev)
COPY . .
RUN npm run build                # 產出 /app/dist
 
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev            # 只裝正式依賴
COPY --from=builder /app/dist ./dist   # ← 關鍵這行
USER node
CMD ["node", "dist/server.js"]

前面的 stage 只是臨時工作區,整層被丟掉:1.2GB → 約 60MB。

極端案例

  • Go + FROM scratch(完全空白的 image)→ 最終可能只有 8MB,裡面連 shell 都沒有,攻擊面趨近於零
  • React / Vue → builder 產出靜態檔,第二階段用 nginx:alpine 提供,最終完全不需要 Node

其他瘦身手段

基底選 -alpine-slimnpm ci --omit=devpip install --no-cache-dir/同一個 RUN 裝完就清快取/寫好 .dockerignore/用 docker history 找肥的那層(見 Dockerfile 分層快取由上往下連鎖失效)。

⚠️ Alpine 用 musl libc,少數需要編譯原生模組的套件會有相容性問題,不確定就先用 -slim(Debian 精簡版)折衷。