iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Vibe Coding

Vibe Mode 開啟:30 天用 AI 打造網頁,邊做邊學 JavaScript系列 第 24 篇

Day 24:Docker 容器化:指示 AI 寫出最佳化的 Dockerfile 與 docker-compose

  • 分享至 

  • xImage
  •  

今日目標

經過前面四周的開發、AI 功能整合與資安補強,我們的 AI 個人財務追蹤器已經是一套功能齊全且具備防禦力的現代 Web 應用。今天我們正式邁入階段五:CI/CD、部署與運運落地!

在上雲部署前,最大的痛點往往是「在我電腦上明明可以跑(It works on my machine)」。為了解決跨環境的依賴問題,我們需要將 Next.js 前端、Node.js 後端與 PostgreSQL 資料庫完全容器化(Containerization)。

今天我們的目標是運用 AI 幫我們撰寫具備多階段構建(Multi-stage Build)、極小化鏡像體積的 Dockerfile,並結合 docker-compose.yml 打造一鍵啟動的全棧本地與預發佈環境!


Human vs. AI 實戰記錄

傳統撰寫 Dockerfile 需要對 Linux 映像檔、Alpine 底層包、Node.js 快取機制與獨立輸出(Standalone Output)有深厚瞭解。讓 AI 來做這件事雖然快,但如果給的 prompt 不夠精確,AI 很可能會打包出高達 1.5 GB 的巨無霸 Docker 鏡像,甚至把敏感環境變數直接硬編碼(Hard-code)進包裡!


1. 給 AI 的指令 (Prompt)

我們希望 AI 幫我們為 Next.js + Prisma 專案建立高效能、安全的生產級 Dockerfile,並搭配 PostgreSQL 與 Redis 的 docker-compose 檔案。

你是一位資深 DevOps 工程師。請幫我的 Next.js ( App Router + Prisma ORM ) 專案撰寫 Docker 容器化配置。

需求如下:
1. 撰寫 Production-ready 的 `Dockerfile`:
   - 使用 Multi-stage Build(deps, builder, runner)以最小化最終 Image 體積。
   - 包含 Next.js 的 standalone 模式優化。
   - 確保 Prisma Client 可以正確生成並被 runner 階段讀取。
   - 基於安全考慮,使用非 root 用戶(nextjs:nodejs)運行應用。
2. 撰寫 `docker-compose.yml`:
   - 包含 app 本體、PostgreSQL 16 與 Redis (作為快取與 Rate Limit)。
   - 設定 healthcheck 與正確的依賴順序 (depends_on)。


2. AI 產出的結果 (Code)

AI 給出了結構看起來相當專業的多階段 Dockerfile 與 docker-compose.yml:

# AI 生成的 Dockerfile
FROM node:20-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000
CMD ["node", "server.js"]


3. 修正與翻車紀錄 (Debug Experience)

這份 Dockerfile 表面上使用了 Multi-stage Build,但在實際執行 docker build 與 docker-compose up 時,卻踩到了兩個極為棘手的容器化巨坑!

翻車點一:Prisma Native Binary 遺失與 OpenSSL 版本不匹配

當容器運行到 runner 階段啟動 server.js 時,突然跳出經典報爆錯:
Error: PrismaClientInitializationError: Prisma Client could not locate the Query Engine syslib.

原因:Alpine Linux 使用的是 musl libc,而 Prisma 在 builder 階段編譯時生成了對應的二進位檔案,但在 runner 階段 AI 沒有將 .prisma/client 或 prisma 相關的引擎檔跟 .next/standalone 一起複製過去!此外,Alpine 缺少 OpenSSL 相關依賴庫。

翻車點二:docker-compose 資料庫競態條件 (Race Condition)

在 docker-compose.yml 中,AI 寫了 depends_on: [postgres],但 PostgreSQL 容器啟動(Container Created)不等於資料庫已經準備好接受連線(Database Ready)。結果 app 容器一開起來就因為連不上 DB 斷開並拋出 ECONNREFUSED 崩潰!

介入修復!

我們引導 AI 修正 Alpine 依賴包、補齊 Prisma 檔案複製,並在 docker-compose 加入嚴格的 healthcheck 檢查:

  1. Alpine 基礎包補全:安裝 libc6-compat 與 openssl。
  2. Prisma 檔案明確轉移:確保 node_modules 中的 .prisma 生成物被完整轉移至 runner 階段。
  3. Docker Compose Healthcheck:設定 PostgreSQL 必須傳回 pg_isready 狀態後,App 才能開始執行啟動命令。

修正後的配置:

修正後的 Dockerfile:

FROM node:20-alpine AS base
# 補齊 Alpine 缺少的 C 函式庫與 OpenSSL 支援 Prisma
RUN apk add --no-cache libc6-compat openssl

FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
COPY prisma ./prisma/
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 產生 Prisma 客戶端二進位檔
RUN npx prisma generate
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
# 關鍵:複製 standalone 目錄與 static 檔案
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# 複製生成的 Prisma 引擎與 Client 防止執行期找不到引擎
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma

USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

修正後的 docker-compose.yml:

version: '3.8'

services:
  postgres:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: finance_db
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myuser -d finance_db"]
      interval: 5s
      timeout: 5s
      retries: 5

  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: "postgresql://myuser:mypassword@postgres:5432/finance_db?schema=public"
      NODE_ENV: "production"
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  postgres_data:


JavaScript / AI 學習小結

  1. Next.js Standalone Output:
    在 next.config.js 開啟 output: 'standalone' 後,Next.js 會自動追蹤 import 並打包最精簡的 node_modules。最終鏡像體積從原本直衝 1.2 GB 暴減至 180 MB,構建速度與部署效率翻倍!
  2. 容器化不只是把檔塞進去,更是運行時的生命週期管理:
    AI 給的 Dockerfile 往往是「靜態」的。涉及資料庫連線(Prisma Native Engine)、系統層級二進位依賴(Alpine vs Debian)以及容器間啟動時間差(Healthcheck)時,工程師的經驗才是讓 Docker Compose 不會在正式環境無限重啟的關鍵。

這樣我們就獲得隨處可執行的 Docker 映像檔~
明天 Day 25,我們將指示 AI 撰寫 GitHub Actions Workflow,實現每次 Git Push 自動跑測試與打包 Docker 的 CI/CD Pipeline!


上一篇
Day 23:資安防護實戰:讓 AI 幫你的程式碼做 Security Audit(防範 XSS, CSRF, SQLi)
下一篇
# Day 25:CI/CD Pipeline 建立:使用 GitHub Actions 自動化執行 Test 與 Build
系列文
Vibe Mode 開啟:30 天用 AI 打造網頁,邊做邊學 JavaScript 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言