---
# System prepended metadata

title: "\U0001F4CB 初探 Docker Render 環境部署 CI/CD"
tags: [Docker, Testing, Github Action, PostreSQL, CI/CD]

---

# 📋 初探 Docker Render 環境部署 CI/CD

> 基於 Next.js 15 + React 19 + TypeScript 的全端 To-Do List 應用開發練習
> Github: https://github.com/Yo0GuitarIT/to-do-list-2025
> Demo: https://to-do-list-2025.onrender.com

## 📖 目錄

- 1. 建立基礎應用
- 2. 數據持久化設計
- 3. 加入測試
- 4. Docker 容器化
- 5. 建置 CI/CD
- 6. 部署上 Render 雲端
- 7. 學習成果總結

---

## 1. 建立基礎應用

### 🏗️ 專案初始化

**Git 提交歷程：**
```bash
bc9ffec Initial commit from Create Next App
b66538c feat: reset style 移除 globals.css 和 page.tsx 中的多餘樣式與內容，簡化結構
```

### ⚙️ 關鍵配置

**Next.js 配置為 Docker 部署做準備：**
```typescript
// next.config.ts
const nextConfig: NextConfig = {
  output: "standalone",  // 關鍵：為 Docker 部署啟用獨立模式
  serverExternalPackages: ["@prisma/client", "prisma"],
};
```

### 💡 重點注意事項

1. **standalone 模式的重要性**
   - 減少 Docker 映像大小（從 GB 級降到 MB 級）
   - 包含所有必要依賴，無需 node_modules
   - 生產環境的關鍵優化

2. **清理預設內容的原因**
   - 避免不必要的樣式衝突
   - 為自定義設計提供乾淨基礎
   - 減少專案複雜度

---

## 2. 數據持久化設計

### 🗄️ 資料庫選擇挑戰

**遇到的問題：**
- 本地沒有安裝 PostgreSQL
- 不想在系統安裝額外軟體
- 需要保持開發環境乾淨

**解決方案：Docker 容器化 PostgreSQL**

### 📊 Prisma Schema 設計

```prisma
// prisma/schema.prisma - 簡潔的數據模型
model Todo{
  id        Int      @id @default(autoincrement())
  title     String
  completed Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  @@map("todos")  // 映射到 todos 表
}
```

### 🔌 Prisma 客戶端單例模式

```typescript
// src/lib/prisma.ts - 避免開發環境重複連接
const globalForPrisma = globalThis as unknown as{
    prisma: PrismaClient| undefined;
}

export const prisma = globalForPrisma.prisma || new PrismaClient();

if (process.env.NODE_ENV !== "production") {
    globalForPrisma.prisma = prisma;  // 開發環境重用連接
}
```

### ⚠️ 關鍵問題與解決

**1. 開發階段的手動管理問題：**
```bash
# 早期的麻煩操作
docker run --name postgres-todo -e POSTGRES_USER=todouser ... -d postgres:17
npm run dev  # 需要兩個終端
```

**問題點：**
- 每次都要手動啟動 PostgreSQL 容器
- 容器停止後數據可能遺失
- 團隊成員環境不一致

**2. Prisma 連接池問題：**
- Next.js 開發模式會重新載入模組
- 造成多個 Prisma 客戶端實例
- 解決：使用全局變數保持單例

### 💡 重點注意事項

1. **環境變數管理**
   - 本地開發使用 `.env.local`
   - 生產環境使用平台環境變數
   - 絕不將敏感資訊提交到 Git

2. **資料庫連接字串格式**
   ```
   DATABASE_URL="postgresql://user:password@host:port/database"
   ```

---

## 3. 加入測試

### 🧪 測試工具選擇考量

**為什麼選擇 Vitest 而非 Jest：**
- 與 Vite 生態系統整合更好
- 更快的測試執行速度
- 原生 ESM 支援
- 更好的 TypeScript 支援

### 📋 三層測試架構

**測試策略：**
```
頁面測試 (E2E-like) ↑ 少量但重要
組件測試 (Integration) ↑ 中等數量 
API 測試 (Unit) ↑ 大量且詳細
```

### ⚠️ 測試遇到的問題

**1. Next.js 模組 Mock 困難：**
```typescript
// 解決方案：在 setup.ts 中統一 Mock
vi.mock("next/router", () => ({
  useRouter: () => ({ push: vi.fn(), pathname: "/" }),
}));
vi.mock("next/navigation", () => ({
  useRouter: () => ({ push: vi.fn(), pathname: "/" }),
}));
```

**2. Prisma Mock 策略：**
```typescript
// 直接 Mock 整個 Prisma 模組
const mockPrisma = {
  todo: {
    findMany: vi.fn(),
    create: vi.fn(),
    // ... 其他方法
  },
};
vi.mock("@/lib/prisma", () => ({ prisma: mockPrisma }));
```

**3. 異步測試的等待問題：**
- 使用 `waitFor` 等待異步操作完成
- 設定合理的 timeout
- 避免使用 `act` 包裝（React 18+ 自動處理）

### 💡 重點注意事項

1. **測試覆蓋率 vs 測試品質**
   - 重視關鍵路徑測試
   - 包含錯誤處理測試
   - 測試使用者實際操作流程

2. **Mock 策略**
   - 只 Mock 外部依賴
   - 保持測試的可讀性
   - 避免過度 Mock 導致測試失去意義

---

## 4. Docker 容器化

### 🏗️ 容器架構決策

**選擇多容器分離架構的原因：**
- 符合微服務架構原則
- 可以獨立擴展和維護
- 雲端部署更靈活

### 📦 Dockerfile 多階段建置

```dockerfile
# 關鍵階段
FROM node:18-alpine AS deps    # 安裝依賴
FROM base AS builder           # 建置應用 + 生成 Prisma
FROM base AS runner           # 精簡運行環境

# 安全性考量
RUN adduser --system --uid 1001 nextjs
USER nextjs  # 非 root 用戶運行
```

### 🐳 Docker Compose 配置重點

```yaml
# 關鍵配置
services:
  db:
    image: postgres:17-alpine
    healthcheck:  # 確保資料庫就緒
      test: ["CMD-SHELL", "pg_isready -U todouser -d todolist"]
  app:
    depends_on:
      db:
        condition: service_healthy  # 等待資料庫健康
```

### ⚠️ 容器化過程中的問題

**1. 資料庫啟動順序問題：**
```bash
# 問題：應用在資料庫準備好之前啟動
# 解決：使用 healthcheck 和 depends_on
```

**2. Prisma 生成問題：**
```dockerfile
# 問題：運行階段找不到 Prisma 客戶端
# 解決：在 builder 階段生成，然後複製到 runner
RUN npx prisma generate
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
```

**3. 環境變數傳遞問題：**
```yaml
# 注意：容器內的主機名稱
DATABASE_URL=postgresql://todouser:todopassword@db:5432/todolist
#                                                ^^
#                                          服務名稱，非 localhost
```

**4. 資料持久化問題：**
```yaml
# 解決：使用 Docker volumes
volumes:
  - postgres_data:/var/lib/postgresql/data
```

### 🔄 開發體驗轉變

**轉變過程：**
```bash
# 之前：多步驟手動操作
docker run postgres... 
npm run dev

# 之後：一鍵啟動
docker-compose up
```

**帶來的好處：**
- 環境一致性：團隊成員使用相同環境
- 簡化操作：一條指令啟動所有服務
- 隔離性：不污染本地系統

```
┌─────────────────┐    ┌──────────────────┐
│   todo-app      │────│  todo-postgres   │
│   (Web Service) │    │  (Database)      │
│   - Docker      │    │  - Managed PG    │
│   - Next.js     │    │  - Auto backup   │
│   - Port 3000   │    │  - SSL enabled   │
└─────────────────┘    └──────────────────┘
```

### 💡 重點注意事項

1. **Docker 映像大小優化**
   - 使用 Alpine Linux 基礎映像
   - 多階段建置移除開發依賴
   - 啟用 Next.js standalone 模式

2. **安全性考量**
   - 使用非 root 用戶
   - 最小權限原則
   - 不在映像中包含敏感資訊

3. **網路配置**
   - 容器間使用服務名稱通信
   - 適當的埠口映射
   - 網路隔離

---

## 5. 建置 CI/CD

### 🔧 CI/CD 流程設計

**設計思路：**
```
品質檢查 → 測試 → 建置 → 部署
    ↓        ↓      ↓       ↓
   ESLint   Vitest  Build   Render
```

### ⚠️ CI/CD 建置過程中的重大問題

**Git 提交歷程反映的除錯過程：**
```bash
6dda445 feat: add CI/CD pipeline with testing, building, and deployment steps
f1715fe fix: add load parameter to make Docker image available for testing ← 關鍵問題
1f76780 fix: improve Docker testing in GitHub Actions workflow
bfd6949 fix: resolve ESLint errors in GitHub Actions
34ed36f refactor: update cicd
```

### 🐛 具體問題與解決方案

**1. Docker 映像在 CI 中不可用問題：**
```yaml
# 問題：build 後的映像無法在同一工作流中使用
- name: Build Docker image
  uses: docker/build-push-action@v5
  with:
    context: .
    # 缺少這個參數導致映像無法載入
    
# 解決方案：
  with:
    context: .
    load: true  # 關鍵修復！讓映像可用於本地測試
    tags: todo-app:test
```

**問題原因：**
- `docker/build-push-action` 預設不會載入映像到本地
- 只是建置並推送到 registry
- 需要 `load: true` 讓映像在 runner 中可用

**2. PostgreSQL 服務協調問題：**
```yaml
# 問題：應用容器無法連接到 GitHub Actions 的 PostgreSQL 服務
# 解決：正確配置 healthcheck 和網路
services:
  postgres:
    options: >-
      --health-cmd pg_isready
      --health-interval 10s
      --health-timeout 5s
      --health-retries 5
```

**3. ESLint 配置在 CI 環境的嚴格性：**
```javascript
// 問題：本地開發忽略的格式問題在 CI 中失敗
// 解決：調整 ESLint 配置
const eslintConfig = [
  ...compat.extends("next/core-web-vitals", "next/typescript"),
  {
    ignores: ["src/test/vitest.d.ts"], // 忽略測試類型定義
  },
];
```

**4. 環境變數在不同階段的管理：**
```yaml
# CI 測試環境
env:
  DATABASE_URL: postgresql://testuser:testpassword@localhost:5432/testdb

# 部署階段使用 Render 提供的環境變數
# 通過 secrets 管理敏感資訊
```

### 🔄 除錯過程學習

**真實的開發體驗：**
1. **初次建置**：基本功能能跑，但有隱藏問題
2. **第一次失敗**：Docker 映像無法使用 → 加入 `load: true`
3. **第二次失敗**：測試環境配置問題 → 調整 healthcheck
4. **第三次失敗**：ESLint 嚴格檢查 → 修復程式碼規範
5. **重構優化**：流程穩定後進行效率優化

### 💡 重點注意事項

1. **CI/CD 的漸進式建置**
   - 先讓基本流程跑通
   - 逐步解決出現的問題
   - 不要一次做太多變更

2. **環境差異處理**
   - 本地開發 vs CI 環境的差異
   - 網路配置不同
   - 資源限制不同

3. **錯誤訊息解讀**
   - GitHub Actions 的日誌很詳細
   - 重點關注 exit code 和錯誤訊息
   - 分階段除錯，不要同時改多個地方

---

## 6. 部署上 Render 雲端

### ☁️ Render 選擇考量

**為什麼選擇 Render：**
- 有免費方案適合學習
- 原生支援 Docker
- 自動 SSL 證書
- 簡單的環境變數管理
- 與 GitHub 整合良好

### 🔧 Render 配置策略

```yaml
# render.yaml - Infrastructure as Code
services:
  - type: web
    name: todo-app
    runtime: docker  # 直接使用我們的 Dockerfile
    envVars:
      - key: DATABASE_URL
        fromDatabase:  # 自動從資料庫服務獲取連接字串
          name: todo-postgres
          property: connectionString
          
  - type: pserv  # 託管 PostgreSQL
    name: todo-postgres
    plan: free
```

### ⚠️ 部署過程中的關鍵問題

**1. Auto Deploy 的控制問題：**
```
問題：Render 預設會自動部署每次 Git 推送
影響：與 GitHub Actions CI/CD 衝突
解決：關閉 Render 的 Auto Deploy，改用 API 控制
```

**2. 環境變數管理：**
```yaml
# 問題：本地和生產環境的環境變數不同
# 解決：使用 Render 的環境變數自動注入
envVars:
  - key: DATABASE_URL
    fromDatabase:
      name: todo-postgres
      property: connectionString  # Render 自動生成正確的連接字串
```

**3. Docker 建置 Context 問題：**
```yaml
# 確保 Dockerfile 路徑正確
dockerfilePath: ./Dockerfile  # 相對於 repository 根目錄
```

**4. 健康檢查配置：**
```yaml
# Render 需要知道如何檢查應用是否健康
healthCheckPath: /  # 檢查根路徑是否回應 200
```

### 🚀 自動部署整合

**GitHub Actions 與 Render API 整合：**
```yaml
# CI/CD 流程
質量檢查 → 測試 → 建置 → (如果是 main 分支) → 觸發 Render 部署
```

**關鍵配置：**
```yaml
deploy:
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
  steps:
    - name: 🚀 Trigger Render deployment
      run: |
        curl -X POST \
          -H "Authorization: Bearer ${{ secrets.RENDER_API_KEY }}" \
          "https://api.render.com/v1/services/${{ secrets.RENDER_SERVICE_ID }}/deploys"
```

### 🔐 Secrets 管理

**需要設定的 GitHub Secrets：**
```
RENDER_API_KEY=rnd_xxx...        # Render API 金鑰
RENDER_SERVICE_ID=srv-xxx...     # Render 服務 ID
```

**取得方式：**
1. Render Dashboard → Account Settings → API Keys
2. Render Dashboard → Service → Settings → Service ID

### 💡 重點注意事項

**1. 分階段部署策略：**
- 先手動部署確認基本功能
- 再整合自動化 CI/CD
- 避免一次性整合太多變數

**2. 監控和除錯：**
- Render 提供詳細的建置和運行日誌
- 注意資源使用限制（免費方案有限制）
- 設定適當的健康檢查

**3. 成本考量：**
- 免費方案的限制和休眠機制
- 資料庫的連接數限制
- 適合學習和展示，生產環境需評估

---

## 7. 學習成果總結

### 🎯 技術成長軌跡

**從簡單到複雜的學習路徑：**
```
基礎應用 → 資料庫整合 → 測試建立 → 容器化 → CI/CD → 雲端部署
   ↓           ↓           ↓         ↓        ↓        ↓
 React      Prisma      Vitest    Docker   GitHub   Render
 基礎        ORM         測試      容器     Actions   雲端
```

### 🏆 關鍵決策與學習

**1. 技術選擇的考量：**
- **Next.js 15**：最新功能，學習前沿技術
- **Prisma**：類型安全的 ORM，提升開發效率
- **Docker**：解決環境一致性問題
- **Render**：簡單易用的雲端平台

**2. 架構決策的智慧：**
- **多容器分離**：而非單體容器
- **多階段建置**：優化映像大小和安全性
- **漸進式測試**：三層測試金字塔
- **自動化部署**：減少人為錯誤

### 🚧 遇到的挑戰與收穫

**主要挑戰類別：**

1. **環境一致性問題**
   - 本地 vs 容器 vs CI vs 生產環境
   - 學會：環境變數管理、Docker 網路配置

2. **工具整合複雜性**
   - Next.js + Prisma + Docker + GitHub Actions + Render
   - 學會：分階段除錯、問題隔離

3. **CI/CD 調校困難**
   - Docker 映像載入、PostgreSQL 服務協調
   - 學會：讀懂錯誤訊息、漸進式修復

### 📈 實際應用價值

**完整的現代化開發流程：**
```mermaid
graph LR
    A[本地開發] --> B[程式碼推送]
    B --> C[自動測試]
    C --> D[自動建置]
    D --> E[自動部署]
    E --> F[線上服務]
```

**學會的核心技能：**
- ✅ **全端開發**：前後端整合開發
- ✅ **DevOps 實務**：從開發到部署的完整鏈路
- ✅ **問題診斷**：系統性解決複雜問題
- ✅ **工具整合**：多種工具的協調使用

### 🔮 後續發展方向

**可以延伸的學習：**
1. **效能優化**：快取策略、資料庫優化
2. **安全強化**：身份驗證、授權機制
3. **監控告警**：APM 工具、日誌分析
4. **擴展功能**：實時更新、離線支援

### 💡 關鍵心得

**最重要的學習：**
1. **漸進式開發**：不要一次做太多變更
2. **問題隔離**：分層次解決問題
3. **文檔記錄**：記錄每個決策的原因
4. **持續優化**：系統可以持續改進
