Node Docker Image 瘦身:從 1.2GB 到 78MB 的六層優化順序
整理 Threads 與 The Practical Developer 的 Node.js Docker image 瘦身案例:base image、.dockerignore、多階段建構、layer cache、Alpine runtime、distroless,並補上 Alpine/musl 與 distroless 除錯限制。
這則 Threads 摘要了一篇 Practical Developer 的案例:一個 Node.js + TypeScript service,透過六個步驟把 image 從 1.21GB 降到 78MB。真正可複用的重點不是「一律用 Alpine / distroless」,而是按風險由低到高處理:base image、.dockerignore、多階段建構、layer cache,最後才評估 Alpine 與 distroless。
node:22 → node:22-slim,約 1.21GB 降到 412MB。這是低風險高收益,先移除大多數 runtime 不需要的工具鏈。
避免 node_modules、.git、.env、coverage、build artifacts 被 COPY 進 image layer。這同時是 size、cache 與安全問題。
builder stage 保留 TypeScript / dev dependencies;runtime stage 只複製 dist、production node_modules 與 package.json,搭配 npm prune --omit=dev。
先 COPY package*.json 再 npm ci,最後才 COPY source code。大小不變,但改 code 時不會讓 dependency install cache 失效。
| 步驟 | 大小變化 | 主要收益 | 風險 |
|---|---|---|---|
| node:22 → node:22-slim | 1.21GB → 412MB | 移除不必要 OS/toolchain | 低 |
| .dockerignore | 412MB → 388MB | 避免 secrets / local artifacts 進 image | 低 |
| multi-stage + prune | 388MB → 198MB | 編譯依賴不進 runtime | 低到中 |
| cache ordering | 大小不變,重建 94s → 18s | CI/CD rebuild 快很多 | 低 |
| Alpine runtime | 198MB → 96MB | 更小 runtime | 中:musl libc 相容性 |
| Distroless | 96MB → 78MB | 最小攻擊面,無 shell / package manager | 中:除錯與相依檔案要更嚴謹 |
- 先做 node:slim、.dockerignore、多階段建構與 npm ci/cache ordering;這幾步幾乎不改架構,收益最大。