Skip to content

Docker 環境測試與部署

完成 MVP 開發後,用 Docker 測試確保應用可以正確打包和部署。

Step 1:更新 next.config.ts

啟用 standalone 輸出模式:

typescript
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  output: 'standalone',
};

export default nextConfig;

為什麼需要 standalone?

standalone 模式會產生一個最小化的 Node.js server,包含所有必要的依賴,非常適合 Docker 部署。

Step 2:建立 Dockerfile

在專案根目錄建立 Dockerfile

dockerfile
FROM node:22-alpine AS base

# 安裝依賴
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json pnpm-lock.yaml* ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile

# 建構應用
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# 設定環境變數(建構時)
ENV NEXT_TELEMETRY_DISABLED=1

RUN corepack enable pnpm && pnpm 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

# 設定正確的權限
RUN mkdir .next
RUN chown nextjs:nodejs .next

# 複製 standalone 輸出
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

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

Step 3:建立 .dockerignore

避免複製不必要的檔案:

# .dockerignore
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git
.env*.local

Step 4:本地測試 Docker

建構映像

bash
docker build -t my-app .

運行容器

bash
# 基本運行
docker run -p 3000:3000 my-app

# 帶環境變數運行
docker run -p 3000:3000 \
  -e NEXT_PUBLIC_SUPABASE_URL=http://host.docker.internal:54321 \
  -e NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key \
  my-app

host.docker.internal

在 Docker 中連接本地 Supabase,使用 host.docker.internal 而不是 localhost127.0.0.1

驗證應用

開啟瀏覽器訪問 http://localhost:3000,確認:

  1. 頁面正常顯示
  2. 資料可以讀取
  3. 表單可以提交
  4. Auth 功能正常

Step 5:用 docker-compose(可選)

如果需要同時運行多個服務:

yaml
# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
      - NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}
    depends_on:
      - supabase

  supabase:
    image: supabase/postgres:15.1.0.117
    # 這只是示例,實際使用 npx supabase start

運行:

bash
docker-compose up

Step 6:推送到 GitHub

確認 Docker 測試通過後,推送程式碼:

bash
# 如果還沒初始化 Git
git init
git add .
git commit -m "feat: MVP complete with Docker support"

# 連接 GitHub repo
git remote add origin https://github.com/your-username/your-app.git
git branch -M main
git push -u origin main

Step 7:部署到 Vercel

方法 1:Vercel Dashboard(推薦)

  1. 前往 vercel.com
  2. 使用 GitHub 登入
  3. 點擊「Add New...」>「Project」
  4. 選擇你的 GitHub repo
  5. 設定環境變數
  6. 點擊「Deploy」

方法 2:Vercel CLI

bash
# 安裝 CLI
npm install -g vercel

# 登入
vercel login

# 部署
vercel

# Production 部署
vercel --prod

環境變數設定

在 Vercel Dashboard 設定 Production 環境變數:

變數名稱說明
NEXT_PUBLIC_SUPABASE_URLhttps://xxx.supabase.coSupabase 雲端 URL
NEXT_PUBLIC_SUPABASE_ANON_KEYeyJhbGci...Supabase Anon Key

取得 Supabase 雲端資訊

  1. 前往 supabase.com
  2. 建立新專案或選擇現有專案
  3. 前往 Settings > API
  4. 複製 Project URLanon public key

重要

Production 要使用 Supabase 雲端專案,不是本地的!

記得把本地的 migration 推送到雲端:

bash
npx supabase db push

持續部署

Vercel 會自動監聽 GitHub 變更:

事件行為
推送到 mainProduction 部署
推送到其他分支Preview 部署
開 Pull RequestPreview 部署

常見問題

Docker build 失敗:pnpm 錯誤

確認 corepack enable pnpm 在 Dockerfile 中:

dockerfile
RUN corepack enable pnpm && pnpm install --frozen-lockfile

Docker run 連不到 Supabase

使用 host.docker.internal

bash
docker run -p 3000:3000 \
  -e NEXT_PUBLIC_SUPABASE_URL=http://host.docker.internal:54321 \
  my-app

Vercel build 失敗

  1. 確認本地 pnpm build 成功
  2. 確認本地 pnpm lint 無錯誤
  3. 檢查環境變數是否正確設定

環境變數沒生效

  1. 變數名稱大小寫敏感
  2. 前端用的要加 NEXT_PUBLIC_ 前綴
  3. 設定後要重新部署

部署檢查清單

  • [ ] pnpm build 成功
  • [ ] pnpm lint 無錯誤
  • [ ] Docker build 成功
  • [ ] Docker run 應用正常
  • [ ] Supabase migration 已推送到雲端
  • [ ] Vercel 環境變數已設定
  • [ ] Production 部署成功

效能優化(可選)

Vercel Analytics

bash
pnpm add @vercel/analytics
typescript
// app/layout.tsx
import { Analytics } from '@vercel/analytics/react';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-TW">
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  );
}

Speed Insights

bash
pnpm add @vercel/speed-insights
typescript
// app/layout.tsx
import { SpeedInsights } from '@vercel/speed-insights/next';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-TW">
      <body>
        {children}
        <SpeedInsights />
      </body>
    </html>
  );
}

恭喜!

你的應用已經上線了!

記得:Ship first, perfect later

接下來可以:

  • 收集使用者回饋
  • 迭代改進功能
  • 優化效能
  • 加入更多功能

AI 時代的軟體工程工作坊教學手冊