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
後端 APICampus Activity Backend(Spring Boot)
認證NTUB IMD BIRC SSO + JWT

相關檔案一覽

檔案用途
Dockerfile多階段建置,產出可部署的 Next.js 映像
docker-compose.prod.yml生產環境部署(從 Harbor 拉取映像)
.dockerignore建置映像時排除的檔案
.gitlab-ci.ymlGitLab 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.jsonpackage-lock.json,執行 npm ci 安裝依賴(獨立 Layer,利於 Docker Cache)
builder複製原始碼,透過 ARG NEXT_PUBLIC_API_URL 注入環境變數,執行 npm run build
runner複製 .nextpublicnode_modulespackage.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)

執行階段

  1. 使用精簡的 Alpine 映像(node:22-alpine
  2. 建立非 root 使用者 nextjs(uid 1001)
  3. 監聽 3000
  4. 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 lintESLint 檢查

本地開發環境變數範例(.env.local):

NEXT_PUBLIC_API_URL=http://localhost:8080/api
NEXT_PUBLIC_SSO_BASE_URL=https://sso.ntubimdbirc.twCode language: JavaScript (javascript)

驗證:

生產環境(docker-compose.prod.yml

適用於伺服器部署,從 Harbor 拉取已建置好的映像。

服務映像說明
frontendharbor.ntubimdbirc.tw/teaching-platform/activity-system-frontend:${APP_TAG:-latest}正式前端應用程式

生產環境特點:

  • 使用預先建置並推送至 Harbor 的映像,不本地編譯
  • NODE_ENV=production
  • 容器日誌採 json-file,單檔上限 10MB、保留 3 個檔案
  • 透過 .env 注入 APP_TAGFRONTEND_PORTNEXT_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_URLSSO 登入中心 Base URL(選填)https://sso.ntubimdbirc.tw

生產環境(伺服器 .env

變數說明
APP_TAGHarbor 映像標籤(如 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 devnpm run start(容器內)
映像來源無(直接跑 Node)Harbor 遠端映像
原始碼掛載是(熱重載)
環境變數來源.env.localCI build-arg + 伺服器 .env
執行使用者本機使用者nextjs(非 root)
監聽埠30003000(對外映射 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觸發分支說明
buildbuild-betadevelopment建置 Beta 映像並推送 Harbor
buildbuild-onlinemain建置正式映像並推送 Harbor
deploydeploy-betadevelopmentSSH 部署至 Beta 伺服器
deploydeploy-onlinemainSSH 部署至正式伺服器(手動觸發

映像標籤策略

標籤時機說明
beta-{commit-sha}development 分支每次 PipelineBeta 環境可追溯版本
beta-latestdevelopment 分支最新版作為 --cache-from 加速建置
{commit-sha}main 分支每次 Pipeline正式環境可追溯版本
latestmain 分支最新版正式環境預設標籤

Build Job 流程

  1. 登入 Harbor(HARBOR_USER / HARBOR_PASSWORD
  2. 拉取上一版映像作為建置快取(--cache-from
  3. --build-arg NEXT_PUBLIC_API_URL 建置映像
  4. 推送 {commit-sha}latest / beta-latest 標籤

Deploy Job 流程

  1. 透過 SSH 連線至目標伺服器
  2. 上傳 docker-compose.prod.yml 至部署目錄(/opt/activity-system-frontend$HOME/activity-system-frontend
  3. 更新伺服器 .env 中的 APP_TAG
  4. 登入 Harbor、docker compose pull frontend
  5. docker compose up -d --no-deps frontend(僅更新前端,不影響其他服務)
  6. 健康檢查:curl http://localhost:${FRONTEND_PORT}/
  7. 清理未使用的映像: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_URLHarbor 倉庫位址
HARBOR_USERHarbor 登入帳號
HARBOR_PASSWORDHarbor 登入密碼
NEXT_PUBLIC_API_URL_BETABeta 環境後端 API URL(建置用)
NEXT_PUBLIC_API_URL_PROD正式環境後端 API URL(建置用)
BETA_HOST / BETA_USER / BETA_SSH_KEYBeta 伺服器 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 定義
.dockerignoreDocker 建置排除清單
.gitlab-ci.ymlGitLab CI/CD Pipeline(build + deploy)
next.config.tsNext.js 設定
package.json依賴與 npm scripts
lib/sso.tsSSO 導向、JWT 處理、getApiBaseUrl()
lib/api.ts附帶 Bearer Token 的 API 請求封裝
lib/hooks/AuthContext.tsx全站認證狀態管理
前台SSO技術文件.mdSSO 整合詳細說明

與後端 CI/CD 的協作關係

面向前端(本專案)後端(Campus Activity Backend)
框架Next.js 16Spring Boot 4
容器埠30008080
映像倉庫harbor.../activity-system/frontendharbor.../campus-activity/campus-activity_app
部署方式SSH + docker compose up --no-deps frontendSSH + docker compose up -d
環境變數重點NEXT_PUBLIC_API_URL(建置時注入)SPRING_DATASOURCE_*、SSO Secret 等
CI 觸發development / main 分支develop / main 分支

前後端可獨立部署;前端更新通常不需重啟後端,但變更 API 契約時需協調兩邊版本。