iT邦幫忙

2025 iThome 鐵人賽

0
自我挑戰組

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

Day34 - 持續成長學習藍圖 - Docker(模擬部署環境)

  • 分享至 

  • xImage
  •  

昨天我們已經成功讓 Todo API 跟 PostgreSQL 容器整合,
今天的目標是讓這整個系統更「像在雲端運作」——
我們要模擬生產部署(production environment),
並練習 docker-compose.prod.yml 的設計與啟動流程。

在開發階段,我們通常會:

  • 掛上 volume
  • npm run dev 自動重啟
  • 修改程式碼立即生效

但在「上線部署」時,這些都不需要。
我們希望:

  • 映像小、啟動快
  • 不要帶入 node_modules 快取垃圾
  • 只跑 npm start(production 模式)
  • volume 改成只掛資料(例如 DB)

所以今天要做的事情是:

讓整個專案有「開發用 compose」和「生產用 compose」。


1️⃣ 調整 Dockerfile 為多階段建置(Multi-stage Build)

在專案根目錄,改成這樣的 Dockerfile

# ========== 開發階段 (builder) ==========
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build  # 若是 TypeScript 專案,這會輸出到 dist/

# ========== 生產階段 (runner) ==========
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

# 僅複製必要檔案(避免帶入 dev 依賴)
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/prisma ./prisma

RUN npm ci --only=production

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

這樣會:

  • 在 builder 階段安裝全部依賴並編譯
  • 在 runner 階段只保留執行用的檔案
    👉 最終映像更輕、更乾淨、更安全。

2️⃣ 建立 docker-compose.prod.yml

新增這個檔案:

version: "3.9"

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    image: ts-todo-api:prod
    container_name: ts-todo-api-prod
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/todo
    depends_on:
      - db
    restart: always

  db:
    image: postgres:16-alpine
    container_name: todo-db-prod
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: todo
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: always

volumes:
  db_data:

這份 Compose 就是「生產版」:

  • 不掛 volume(程式碼是固定的)
  • 會自動重啟 (restart: always)
  • 不使用 npm run dev
  • 不保留本機對應資料夾

3️⃣ 建立 production build

docker compose -f docker-compose.prod.yml build

這會使用剛剛的 multi-stage Dockerfile,
生成一個更小的 ts-todo-api:prod image。

你可以檢查大小:

docker images ts-todo-api:prod

4️⃣ 啟動生產環境

docker compose -f docker-compose.prod.yml up -d

檢查容器狀態:

docker compose -f docker-compose.prod.yml ps

輸出:

NAME               SERVICE  STATUS    PORTS
ts-todo-api-prod   api      running   0.0.0.0:3000->3000/tcp
todo-db-prod       db       running   0.0.0.0:5432->5432/tcp

5️⃣ 查看日誌

docker compose -f docker-compose.prod.yml logs -f

會看到:

🚀 Server running on port 3000
Database connected successfully!

一切順利 🎉


6️⃣ 模擬部署與重啟

模擬一次「服務重啟」的情境:

docker compose -f docker-compose.prod.yml restart api

模擬「整個系統重新上線」:

docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d

因為有 restart: always
就算伺服器重啟或 Docker daemon 重新啟動,
容器也會自動復原。


7️⃣ Healthcheck(健康檢查,可選)

在生產環境中,你可以加入 healthcheck:

  api:
    ...
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/todos"]
      interval: 30s
      timeout: 5s
      retries: 3

這樣 Docker 會定期檢查 API 是否正常,
若失敗就標記為 unhealthy。


8️⃣ 結構總覽

ts-todo-api/
├─ src/
│  └─ index.ts
├─ prisma/
│  └─ schema.prisma
├─ Dockerfile
├─ docker-compose.yml         # 開發用
├─ docker-compose.prod.yml    # 生產用
├─ package.json
└─ .env

開發用:

docker compose up

生產用:

docker compose -f docker-compose.prod.yml up -d

9️⃣ 小實驗:比一比開發版與生產版

項目 開發版 生產版
啟動指令 npm run dev npm start
是否掛 volume ✅(方便即時修改) ❌(固定程式)
是否使用 cache ✅(build cache)
是否含 devDependencies
映像大小 約 350MB 約 180MB
重啟策略 restart: always

這樣在真正部署到雲端(例如 EC2、VPS、GCP Run)時,
就能直接使用生產版的 Compose。


🎯 今日收穫 / 學習心得

今天的練習就像是正式走上「部署舞台」。
我學會了:

  • Multi-stage build(讓映像更乾淨)
  • docker-compose.prod.yml 的設計邏輯
  • -f 切換不同環境設定
  • restarthealthchecklogs 的部署操作

整個感覺就像從「學生作品」變成「正式產品」:
容器小、啟動快、重啟穩。


上一篇
Day33 - 持續成長學習藍圖 - Docker(API + DB 完整整合)
系列文
《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》34
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言