Docker CI/CD on Next.js
本文件透過 Campus Activity Frontend(校園活動管理系統前端)專案的容器化架構、本地開發環境、生產部署方式,說明 Next.js CI/CD 流程。
相關文件:前台 SSO 技術文件 · 後端 Docker CI/CD 參考
系統概述
| 項目 | 說明 |
|---|---|
| 應用框架 | Next.js 16.2.4 / React 19 / TypeScript |
| 套件管理 | npm(package-lock.json) |
| 容器基底 | node:22-alpine |
| 映像倉庫 | Harbor — harbor.ntubimdbirc.tw |
| 原始碼倉庫 | GitLab — gitlab.ntubimdbirc.tw/general-hospital/activity-system-frontend |
| 後端 API | Campus Activity Backend(Spring Boot) |
| 認證 | NTUB IMD BIRC SSO + JWT |
相關檔案一覽
| 檔案 | 用途 |
|---|---|
Dockerfile | 多階段建置,產出可部署的 Next.js 映像 |
docker-compose.prod.yml | 生產環境部署(從 Harbor 拉取映像) |
.dockerignore | 建置映像時排除的檔案 |
.gitlab-ci.yml | GitLab CI/CD Pipeline(建置、推送、部署) |
.env / .env.local | 本地開發環境變數(不進入映像) |
Dockerfile 映像建置
採用 多階段建置(Multi-stage Build),分為依賴安裝、編譯建置、執行三個階段。
flowchart LR
A[deps<br/>node:22-alpine] -->|npm ci| B[node_modules]
B --> C[builder<br/>node:22-alpine]
C -->|npm run build| D[.next / public]
D --> E[runner<br/>node:22-alpine]
E --> F[最終映像<br/>nextjs 非 root 使用者執行]Code language: HTML, XML (xml)
建置階段
| 階段 | 說明 |
|---|---|
| deps | 複製 package.json、package-lock.json,執行 npm ci 安裝依賴(獨立 Layer,利於 Docker Cache) |
| builder | 複製原始碼,透過 ARG NEXT_PUBLIC_API_URL 注入環境變數,執行 npm run build |
| runner | 複製 .next、public、node_modules、package.json,以 nextjs 使用者執行 npm run start |
Next.js 建置時環境變數注意事項
NEXT_PUBLIC_* 前綴的變數會在 npm run build 時被內嵌至前端程式碼,因此必須在 Docker 建置階段透過 --build-arg 傳入,而非僅在容器執行時設定。
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
RUN npm run buildCode language: PHP (php)
執行階段
- 使用精簡的 Alpine 映像(
node:22-alpine) - 建立非 root 使用者
nextjs(uid 1001) - 監聽 3000 埠
- 以
npm run start(即next start)啟動生產伺服器
本地手動建置
docker build \
--build-arg NEXT_PUBLIC_API_URL=http://localhost:8080/api \
-t activity-system-frontend:local .Code language: JavaScript (javascript)
.dockerignore
以下內容不會進入映像建置上下文:
node_modules/、.next/(本地快取).git/.env.local、.env.development.local等本地環境檔README.md、除錯日誌
Docker Compose 環境
本地開發
本專案未提供 docker-compose.yml 開發用 Compose 檔。本地開發建議直接使用 Node.js 執行:
npm install
cp .env.example .env.local # 若尚未建立
npm run devCode language: CSS (css)
| 指令 | 說明 |
|---|---|
npm run dev | 開發伺服器(預設 http://localhost:3000,支援熱重載) |
npm run build | 本地驗證生產建置 |
npm run start | 本地執行建置後的產物 |
npm run lint | ESLint 檢查 |
本地開發環境變數範例(.env.local):
NEXT_PUBLIC_API_URL=http://localhost:8080/api
NEXT_PUBLIC_SSO_BASE_URL=https://sso.ntubimdbirc.twCode language: JavaScript (javascript)
驗證:
- 前端首頁:http://localhost:3000
- 需搭配後端 API 與 SSO 登入中心(見 前台 SSO 技術文件)
生產環境(docker-compose.prod.yml)
適用於伺服器部署,從 Harbor 拉取已建置好的映像。
| 服務 | 映像 | 說明 |
|---|---|---|
| frontend | harbor.ntubimdbirc.tw/teaching-platform/activity-system-frontend:${APP_TAG:-latest} | 正式前端應用程式 |
生產環境特點:
- 使用預先建置並推送至 Harbor 的映像,不本地編譯
NODE_ENV=production- 容器日誌採
json-file,單檔上限 10MB、保留 3 個檔案 - 透過
.env注入APP_TAG、FRONTEND_PORT、NEXT_PUBLIC_API_URL等變數
部署步驟:
# 在伺服器部署目錄建立 .env
cat > .env <<'EOF'
APP_TAG=latest
FRONTEND_PORT=3000
NEXT_PUBLIC_API_URL=https://api.example.com/api
TIMEZONE=Asia/Taipei
EOF
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -dCode language: PHP (php)
環境變數
本地開發
| 變數 | 說明 | 範例 |
|---|---|---|
NEXT_PUBLIC_API_URL | 後端 API Base URL(含 /api 前綴) | http://localhost:8080/api |
NEXT_PUBLIC_SSO_BASE_URL | SSO 登入中心 Base URL(選填) | https://sso.ntubimdbirc.tw |
生產環境(伺服器 .env)
| 變數 | 說明 |
|---|---|
APP_TAG | Harbor 映像標籤(如 latest、{commit-sha}、beta-{commit-sha}) |
FRONTEND_PORT | 對外映射的前端 Port |
NEXT_PUBLIC_API_URL | 後端 API Base URL(Compose 執行時注入;建置時亦需透過 CI build-arg 傳入) |
TIMEZONE | 時區(預設 Asia/Taipei) |
建置時 vs 執行時注入
| 變數 | 建置時(--build-arg) | 執行時(Compose environment) |
|---|---|---|
NEXT_PUBLIC_API_URL | ✅ 必須(內嵌至前端 bundle) | ⚠️ 執行時設定無法覆蓋已建置的 bundle |
重要: 若需變更
NEXT_PUBLIC_API_URL,必須重新建置並推送 Docker 映像,僅重啟容器無效。
開發與生產差異對照
| 項目 | 本地開發 | 生產(Docker) |
|---|---|---|
| 啟動方式 | npm run dev | npm run start(容器內) |
| 映像來源 | 無(直接跑 Node) | Harbor 遠端映像 |
| 原始碼掛載 | 是(熱重載) | 否 |
| 環境變數來源 | .env.local | CI build-arg + 伺服器 .env |
| 執行使用者 | 本機使用者 | nextjs(非 root) |
| 監聽埠 | 3000 | 3000(對外映射 FRONTEND_PORT) |
| 後端依賴 | 需本地或遠端後端 | 需可連線的生產後端 API |
CI/CD 流程
本專案已納入 .gitlab-ci.yml,採用 GitLab CI + Harbor + SSH 遠端部署 流程。
flowchart LR
A[Push] --> B{分支判斷}
B -->|development| C[build-beta]
B -->|main| D[build-online]
C --> E[deploy-beta]
D --> F[deploy-online<br/>手動觸發]
C --> G[Push to Harbor]
D --> G
G --> H[SSH 更新遠端 Compose]Code language: HTML, XML (xml)
Pipeline 階段
| 階段 | Job | 觸發分支 | 說明 |
|---|---|---|---|
| build | build-beta | development | 建置 Beta 映像並推送 Harbor |
| build | build-online | main | 建置正式映像並推送 Harbor |
| deploy | deploy-beta | development | SSH 部署至 Beta 伺服器 |
| deploy | deploy-online | main | SSH 部署至正式伺服器(手動觸發) |
映像標籤策略
| 標籤 | 時機 | 說明 |
|---|---|---|
beta-{commit-sha} | development 分支每次 Pipeline | Beta 環境可追溯版本 |
beta-latest | development 分支最新版 | 作為 --cache-from 加速建置 |
{commit-sha} | main 分支每次 Pipeline | 正式環境可追溯版本 |
latest | main 分支最新版 | 正式環境預設標籤 |
Build Job 流程
- 登入 Harbor(
HARBOR_USER/HARBOR_PASSWORD) - 拉取上一版映像作為建置快取(
--cache-from) - 以
--build-arg NEXT_PUBLIC_API_URL建置映像 - 推送
{commit-sha}與latest/beta-latest標籤
Deploy Job 流程
- 透過 SSH 連線至目標伺服器
- 上傳
docker-compose.prod.yml至部署目錄(/opt/activity-system-frontend或$HOME/activity-system-frontend) - 更新伺服器
.env中的APP_TAG - 登入 Harbor、
docker compose pull frontend docker compose up -d --no-deps frontend(僅更新前端,不影響其他服務)- 健康檢查:
curl http://localhost:${FRONTEND_PORT}/ - 清理未使用的映像:
docker image prune -f
.gitlab-ci.yml 摘要
stages:
- build
- deploy
variables:
IMAGE_NAME: $HARBOR_URL/activity-system/frontend
# development → build-beta → deploy-beta(自動)
# main → build-online → deploy-online(手動)Code language: PHP (php)
GitLab CI/CD 變數
需在 GitLab Settings → CI/CD → Variables 設定:
| 變數 | 說明 | 保護 / 遮罩 |
|---|---|---|
HARBOR_URL | Harbor 倉庫位址 | 是 |
HARBOR_USER | Harbor 登入帳號 | 是 |
HARBOR_PASSWORD | Harbor 登入密碼 | 是 |
NEXT_PUBLIC_API_URL_BETA | Beta 環境後端 API URL(建置用) | 是 |
NEXT_PUBLIC_API_URL_PROD | 正式環境後端 API URL(建置用) | 是 |
BETA_HOST / BETA_USER / BETA_SSH_KEY | Beta 伺服器 SSH 連線資訊 | 是 |
ONLINE_HOST / ONLINE_USER / ONLINE_SSH_KEY | 正式伺服器 SSH 連線資訊 | 是 |
FRONTEND_PORT | 前端健康檢查與對外 Port | 否 |
網路架構
本地開發
┌─────────────────────────────────────────┐
│ 開發者本機 │
│ │
│ ┌──────────┐ ┌─────────────────┐ │
│ │ Next.js │─────▶│ Backend API │ │
│ │ :3000 │ │ :8080 │ │
│ └────┬─────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ SSO 中心 │ │
│ └──────────┘ │
└─────────────────────────────────────────┘Code language: CSS (css)
生產環境
┌──────────────────────────────┐
│ Docker Host │
│ ┌──────────────┐ │
│ │ frontend │ │
│ │ :3000 │ │
│ └──────┬───────┘ │
└─────────┼────────────────────┘
│ FRONTEND_PORT 對外映射
▼
反向代理 / 負載平衡器
│
▼
Backend API / SSO
前端為純靜態 + SSR 應用,容器內僅運行 Next.js 服務,資料庫與快取由後端負責。
維運操作
常用指令
# 查看服務狀態
docker compose -f docker-compose.prod.yml ps
# 查看前端日誌
docker compose -f docker-compose.prod.yml logs -f frontend
# 重啟前端(不影響其他服務)
docker compose -f docker-compose.prod.yml restart frontend
# 停止並移除容器
docker compose -f docker-compose.prod.yml downCode language: CSS (css)
健康檢查
| 檢查項目 | 方式 |
|---|---|
| 前端服務 | curl -sf http://localhost:${FRONTEND_PORT}/ |
| 容器狀態 | docker compose -f docker-compose.prod.yml ps 確認 running |
| API 連線 | 瀏覽器開啟首頁,確認活動列表可載入 |
| SSO 登入 | 點擊登入,完成 Google 驗證後可進入個人頁面 |
滾動更新
# 指定映像版本
export APP_TAG=abc1234
docker compose -f docker-compose.prod.yml pull frontend
docker compose -f docker-compose.prod.yml up -d --no-deps frontendCode language: PHP (php)
CI/CD Pipeline 部署時會自動更新伺服器 .env 中的 APP_TAG 並執行上述流程。
常見問題
| 問題 | 可能原因 | 處理方式 |
|---|---|---|
| API 請求 404 / CORS 錯誤 | NEXT_PUBLIC_API_URL 建置時設定錯誤 | 修正 GitLab 變數後重新觸發 Pipeline 建置 |
| 登入後無法取得資料 | 後端未啟動或 JWT 交換失敗 | 檢查後端日誌與 SSO 設定 |
| 容器啟動後立即退出 | Port 衝突或映像損壞 | docker compose logs frontend 查看錯誤 |
| Harbor pull 失敗 | 登入憑證過期或映像不存在 | 確認 APP_TAG 與 Harbor 上的標籤一致 |
原始碼索引
| 檔案 | 職責 |
|---|---|
Dockerfile | 多階段映像建置(deps → builder → runner) |
docker-compose.prod.yml | 生產部署 Compose 定義 |
.dockerignore | Docker 建置排除清單 |
.gitlab-ci.yml | GitLab CI/CD Pipeline(build + deploy) |
next.config.ts | Next.js 設定 |
package.json | 依賴與 npm scripts |
lib/sso.ts | SSO 導向、JWT 處理、getApiBaseUrl() |
lib/api.ts | 附帶 Bearer Token 的 API 請求封裝 |
lib/hooks/AuthContext.tsx | 全站認證狀態管理 |
前台SSO技術文件.md | SSO 整合詳細說明 |
與後端 CI/CD 的協作關係
| 面向 | 前端(本專案) | 後端(Campus Activity Backend) |
|---|---|---|
| 框架 | Next.js 16 | Spring Boot 4 |
| 容器埠 | 3000 | 8080 |
| 映像倉庫 | harbor.../activity-system/frontend | harbor.../campus-activity/campus-activity_app |
| 部署方式 | SSH + docker compose up --no-deps frontend | SSH + docker compose up -d |
| 環境變數重點 | NEXT_PUBLIC_API_URL(建置時注入) | SPRING_DATASOURCE_*、SSO Secret 等 |
| CI 觸發 | development / main 分支 | develop / main 分支 |
前後端可獨立部署;前端更新通常不需重啟後端,但變更 API 契約時需協調兩邊版本。
