iT邦幫忙

2025 iThome 鐵人賽

0
自我挑戰組

《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》系列 第 35

Day35 - 持續成長學習藍圖 - Docker(優化映像與健康檢查)

  • 分享至 

  • xImage
  •  

昨天,我們已經能夠用 docker-compose.prod.yml 模擬生產環境。
今天要更進一步 —— 學會讓映像更小、更快、更穩定,
並加入 Healthcheck(健康檢查) 來確保服務運作正常。

隨著專案越來越完整,我開始在意三件事:

  1. 映像太大:上傳或部署都很慢。
  2. 啟動太慢:CI/CD 測試要等半天。
  3. 服務掛掉沒人發現:沒有監控,會默默失效。

今天的目標就是讓 Docker 容器更像「一台上線中的伺服器」——
精簡、穩定、可被監控。


1️⃣ 為什麼要優化映像

假設我目前的映像大小如下:

REPOSITORY       TAG     SIZE
ts-todo-api      prod    350MB

如果我每次都要 push 到 Docker Hub 或部署到雲端,
這個大小會讓整個流程變得又慢又浪費頻寬。

Docker Image 的最佳化策略大概分為三種:

類別 說明
減少層數 合併多個 RUN 指令
使用輕量基底 例如 node:alpine 取代 node:latest
多階段建置 將 build 階段與執行階段拆開(昨天已做)

2️⃣ 檢查映像層與大小

docker history ts-todo-api:prod

可以看到每一層的指令、大小,例如:

IMAGE          CREATED        CREATED BY                                      SIZE
8e912f9c2f7a   2 hours ago    CMD ["node" "dist/index.js"]                    0B
<missing>      2 hours ago    RUN npm ci --only=production                    105MB
<missing>      2 hours ago    COPY --from=builder /app/dist ./dist            3MB
<missing>      2 hours ago    FROM node:18-alpine                             46MB

🔍 發現 npm ci 層通常是最肥的。
如果你的專案只用到部分套件,可以考慮切分 monorepo 或用 PNPM 壓縮依賴。


3️⃣ 合併 RUN 指令

在 Dockerfile 中,有時我們這樣寫:

RUN apk add --no-cache bash
RUN npm ci

這會建立兩層。
可以改成這樣合併:

RUN apk add --no-cache bash && npm ci

減少層數 = 減少 image 大小。


4️⃣ 進一步精簡:使用 node:18-slimalpine

映像 大小 備註
node:18 約 900MB 含完整工具
node:18-slim 約 300MB 較輕、仍支援常見套件
node:18-alpine 約 180MB 最輕,但部分 native 模組需額外安裝編譯工具

如果你的專案沒有使用原生 C 模組(例如 bcrypt、sharp),
node:18-alpine 幾乎是完美選擇。


5️⃣ 新版 Dockerfile(更極致)

# ========= Build stage =========
FROM node:18-alpine AS builder
WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build

# ========= Run stage =========
FROM node:18-alpine AS runner
WORKDIR /app

# 安全性:刪除不必要權限
RUN addgroup -S app && adduser -S app -G app
USER app

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "dist/index.js"]

💡 新增重點:

  • USER app:避免使用 root 權限執行容器
  • HEALTHCHECK:檢查 /health 是否回應
  • 使用 --omit=dev 只安裝生產依賴

6️⃣ 加入健康檢查 API

在你的 Express 主程式中(例如 src/index.ts)加入:

import express from "express";
const app = express();

app.get("/health", (req, res) => {
  res.status(200).json({ status: "ok" });
});

app.listen(3000, () => console.log("🚀 Server running on port 3000"));

Docker 就會透過 /health 檢查容器是否存活。


7️⃣ 查看健康狀態

啟動容器後:

docker ps

會看到新的欄位:

CONTAINER ID   IMAGE             STATUS                     PORTS
8e1b7d...      ts-todo-api:prod  Up 3 minutes (healthy)     0.0.0.0:3000->3000/tcp

如果 /health 回傳非 200、或無法連線,
Docker 會標記為 (unhealthy)


8️⃣ 模擬錯誤

試著把 /health 改成回傳 500:

app.get("/health", (req, res) => res.status(500).send("fail"));

重新 build + run,
幾分鐘後你會看到:

STATUS: Up 2 minutes (unhealthy)

這代表健康檢查機制成功偵測異常 👏


9️⃣ 清理多餘映像與暫存

開發過程中會產生很多 intermediate layers,
可以用以下方式清理:

docker system prune -a

如果要只清除暫存 build cache:

docker builder prune

🎯 今日收穫 / 學習心得

今天的主題讓我開始理解 Docker「真正的生產觀念」。
不是只有「能跑就好」,而是要穩、快、輕、可監控

✨ 今日關鍵收穫:

  • 多階段建置(Multi-stage build)讓映像更小
  • 非 root 使用者提升安全性
  • HEALTHCHECK 幫助自動監控容器健康
  • 合併 RUN 指令減少層數、加快 build
  • Alpine 版 Node 是最佳輕量選擇

現在的容器就像一台「乾淨又聰明的伺服器」🧠
出錯會自己標記、重新啟動也不怕。


上一篇
Day34 - 持續成長學習藍圖 - Docker(模擬部署環境)
系列文
《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》35
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言