Docker CI/CD on Spring Booot
本文件透過 Campus Activity Backend 專案的容器化架構、本地開發環境、生產部署方式,說明Spring Boot CI/CD 流程。
註: Campus Activity Backend是第一個嘗試使用Docker CICD的專案,值得紀念!!
系統概述
| 項目 | 說明 |
|---|---|
| 應用框架 | Spring Boot 4.0.5 / Java 25 |
| 建置工具 | Gradle(多模組專案) |
| 容器基底 | eclipse-temurin:25(Alpine) |
| 映像倉庫 | Harbor — harbor.ntubimdbirc.tw/campus-activity/ |
| 原始碼倉庫 | GitLab — gitlab.ntubimdbirc.tw/birc-backend/campus-activity-backend |
| 資料庫 | MySQL 8.4 |
| 快取 | Redis(僅本地開發 Compose 啟用) |
相關檔案一覽
| 檔案 | 用途 |
|---|---|
Dockerfile | 多階段建置,產出可部署的 JAR 映像 |
docker-compose.yml | 本地開發環境(熱重載、除錯埠、Adminer) |
docker-compose.prod.yml | 生產環境部署(從 Harbor 拉取映像) |
.env.example | 環境變數範本 |
.dockerignore | 建置映像時排除的檔案 |
application-server.yml | 生產 Profile 設定 |
Dockerfile 映像建置
採用 多階段建置(Multi-stage Build),分為建置階段與執行階段。
flowchart LR
A[builder<br/>eclipse-temurin:25-jdk-alpine] -->|gradle build| B[app.jar]
B --> C[production<br/>eclipse-temurin:25-jre-alpine]
C --> D[最終映像<br/>非 root 使用者執行]建置
- 複製 Gradle Wrapper、
build.gradle、settings.gradle、子模組 - 預先下載依賴(獨立 Layer,利於 Docker Cache)
- 複製
src/並執行./gradlew build -x test -x spotlessCheck - 產出
build/libs/*.jar
執行
- 使用較小的 JRE 映像(
25-jre-alpine) - 建立非 root 使用者
spring - 複製 JAR 為
/app/app.jar - 以
spring使用者執行,監聽 8080
本地手動建置
docker build -t campus-activity_app:local .Code language: CSS (css)
建置.dockerignore
以下內容不會進入映像建置上下文:
.gradle/、build/(各模組 build 目錄)- IDE 設定(
.idea/、.vscode/) .git/application-local.yml、日誌檔
Docker Compose 環境
開發環境(docker-compose.yml)
適用於本地開發,支援 Gradle bootRun 熱重載 與 遠端除錯。
| 服務 | 映像 / 建置 | 說明 |
|---|---|---|
db | mysql:8.4 | 資料庫,含 healthcheck |
redis | redis:latest | 快取服務 |
app | 本地 Dockerfile(target: builder) | 以 gradlew bootRun 啟動,掛載原始碼 |
adminer | adminer:latest | 資料庫管理介面(port 8888) |
開發環境特點:
- 原始碼掛載至
/workspace,修改後可即時重載 - 開放 JDWP 除錯埠(預設
5005) SPRING_JPA_HIBERNATE_DDL_AUTO預設update(自動同步 Schema)- Gradle Cache 使用 Named Volume
campus-activity_gradle_cache
啟動步驟:
cp .env.example .env
# 編輯 .env 填入實際密碼與設定
docker compose build --no-cache
docker compose up -d
# 或建置並啟動
docker compose up -d --buildCode language: CSS (css)
驗證:
- Swagger UI:http://localhost:8080/api/swagger-ui/index.html
- Adminer:http://localhost:8888
- 資料庫連線:
jdbc:mysql://localhost:3306/campus-activity?serverTimezone=Asia/Taipei&useSSL=false&allowPublicKeyRetrieval=true
生產環境(docker-compose.prod.yml)
適用於伺服器部署,從 Harbor 拉取已建置好的映像。
| 服務 | 映像 | 說明 |
|---|---|---|
db | mysql:8.4 | 資料庫,無對外 Port(僅內部網路) |
app | harbor.ntubimdbirc.tw/campus-activity/campus-activity_app:${APP_TAG:-latest} | 正式應用程式 |
生產環境特點:
- 使用預先建置並推送至 Harbor 的映像,不本地編譯
SPRING_JPA_HIBERNATE_DDL_AUTO=validate(禁止自動修改 Schema)- 啟用
SPRING_PROFILES_ACTIVE=server,載入application-server.yml - 日誌寫入 Volume
campus-activity_app_logs(/app/logs) - 容器日誌採
json-file,單檔上限 10MB、保留 3 個檔案
部署步驟:
# 設定生產環境變數(見第 5 節)
export APP_TAG=v1.0.0
export SPRING_PROFILES_ACTIVE=server
# ... 其他變數
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -dCode language: PHP (php)
環境變數
開發環境(.env.example)
| 變數 | 說明 | 預設值 |
|---|---|---|
DB_ROOT_PASSWORD | MySQL root 密碼 | — |
DB_DATABASE | 資料庫名稱 | campus-activity |
DB_USER | 應用程式 DB 帳號 | campus-activity_user |
DB_PASSWORD | 應用程式 DB 密碼 | — |
DB_PORT | MySQL 對外 Port | 3306 |
SPRING_JPA_HIBERNATE_DDL_AUTO | Hibernate DDL 策略 | update |
SPRING_APP_UPLOAD_PATH | 檔案上傳路徑 | /app/upload |
SPRING_SERVER_HOST | 應用程式 Host | http://localhost |
SPRING_SERVER_PORT | 應用程式 Port | 8080 |
SPRING_DEBUG_PORT | JDWP 除錯 Port | 5005 |
TIMEZONE | 時區 | Asia/Taipei |
生產環境
docker-compose.prod.yml 額外需求
| 變數 | 說明 |
|---|---|
APP_TAG | Harbor 映像標籤(如 latest、v1.0.0、commit SHA) |
SPRING_PROFILES_ACTIVE | Spring Profile,生產環境設為 server |
SERVER_PORT | 對外映射的應用程式 Port |
SERVER_IP | 伺服器 IP(application-server.yml 使用) |
應用程式注入的環境變數
Compose 會將以下變數傳入 app 容器:
SPRING_DATASOURCE_URL
SPRING_DATASOURCE_USERNAME
SPRING_DATASOURCE_PASSWORD
SPRING_JPA_HIBERNATE_DDL_AUTO
SPRING_SERVER_HOST / SPRING_SERVER_PORT
SPRING_APP_UPLOAD_PATH
SPRING_DATA_REDIS_HOST
TZ
生產環境另需透過 Secret 管理注入 SSO、JWT 等敏感設定(見 application.yml 中的 ${SSO_CLIENT_SECRET} 等)。
開發與生產差異對照
| 項目 | 開發(docker-compose.yml) | 生產(docker-compose.prod.yml) |
|---|---|---|
| 啟動方式 | gradlew bootRun | java -jar app.jar |
| 映像來源 | 本地建置(builder stage) | Harbor 遠端映像 |
| 原始碼掛載 | 是(熱重載) | 否 |
| 除錯埠 | 開放 5005 | 不開放 |
| Redis | 有 | 無(需另行規劃) |
| Adminer | 有 | 無 |
| DDL 策略 | update | validate |
| Spring Profile | local(預設) | server |
| Swagger UI | 啟用 | 預設關閉 |
| DB 對外 Port | 開放 3306 | 不對外暴露 |
| 執行使用者 | root(builder 內) | spring(非 root) |
CI/CD 流程(建議)
目前儲存庫尚未納入 .gitlab-ci.yml。依專案使用的 GitLab 與 Harbor 基礎設施,建議採用以下 Pipeline 流程。
flowchart LR
A[Push / MR] --> B[Build & Test]
B --> C[Docker Build]
C --> D[Push to Harbor]
D --> E{分支判斷}
E -->|main / tag| F[Deploy 生產]
E -->|develop| G[Deploy 測試]| 階段 | 說明 |
|---|---|
| lint | 執行 ./gradlew spotlessCheck |
| test | 執行 ./gradlew test |
| build | 執行 ./gradlew build -x test |
| docker-build | docker build 產出映像 |
| docker-push | 推送至 harbor.ntubimdbirc.tw/campus-activity/campus-activity_app |
| deploy | SSH 或 Docker Compose 更新遠端服務 |
映像標籤
| 標籤 | 時機 |
|---|---|
{commit-sha} | 每次 Pipeline 建置(可追溯) |
dev | develop 分支最新版 |
latest | main 分支最新版 |
v{x.y.z} | Git Tag 發佈 |
建議 .gitlab-ci.yml 範本
不過目前(2026/06)的CICD還不是很完善,以下為AI建議檔案範本:
stages:
- test
- build
- deploy
variables:
IMAGE_NAME: harbor.ntubimdbirc.tw/campus-activity/campus-activity_app
DOCKER_TLS_CERTDIR: ""
test:
stage: test
image: eclipse-temurin:25-jdk-alpine
script:
- chmod +x ./gradlew
- ./gradlew test spotlessCheck --no-daemon
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH
docker-build-push:
stage: build
image: docker:27
services:
- docker:27-dind
before_script:
- docker login -u "$HARBOR_USER" -p "$HARBOR_PASSWORD" harbor.ntubimdbirc.tw
script:
- docker build -t $IMAGE_NAME:$CI_COMMIT_SHORT_SHA .
- docker tag $IMAGE_NAME:$CI_COMMIT_SHORT_SHA $IMAGE_NAME:latest
- docker push $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
- docker push $IMAGE_NAME:latest
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy-production:
stage: deploy
script:
- ssh deploy@$PROD_SERVER "
cd /opt/campus-activity &&
export APP_TAG=$CI_COMMIT_SHORT_SHA &&
docker compose -f docker-compose.prod.yml pull &&
docker compose -f docker-compose.prod.yml up -d"
environment:
name: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manualCode language: PHP (php)
GitLab CI/CD 變數
需在 GitLab 設定
| 變數 | 說明 | 保護 / 遮罩 |
|---|---|---|
HARBOR_USER | Harbor 登入帳號 | 是 |
HARBOR_PASSWORD | Harbor 登入密碼 | 是 |
PROD_SERVER | 生產伺服器 IP 或 Hostname | 是 |
SSH_PRIVATE_KEY | 部署用 SSH 金鑰 | 是 |
生產 Profile 設定重點
啟用 server Profile 時,載入 application-server.yml:
| 項目 | 設定 |
|---|---|
| Hibernate DDL | validate(不自動變更 Schema) |
| 連線池 | HikariCP max 20 / min 10 |
| 日誌等級 | root WARN,應用 INFO |
| 日誌檔案 | /var/log/campus-activity/application.log(單檔 100MB,保留 30 天) |
| Swagger UI | 預設關閉(ENABLE_SWAGGER_UI=false) |
| API Docs | 預設關閉(ENABLE_API_DOCS=false) |
網路架構
開發環境
┌─────────────────────────────────────────────┐
│ campus-network (bridge) │
│ │
│ ┌──────┐ ┌───────┐ ┌─────┐ ┌────────┐ │
│ │ app │──│ db │ │redis│ │adminer │ │
│ │:8080 │ │:3306 │ │:6379│ │ :8888 │ │
│ └──┬───┘ └───────┘ └─────┘ └────────┘ │
│ │ │
└─────┼───────────────────────────────────────┘
│ :8080, :5005 對外映射
▼
開發者本機Code language: CSS (css)
生產環境
┌──────────────────────────────┐
│ Docker Network │
│ ┌──────┐ ┌──────┐ │
│ │ app │──────│ db │ │
│ │:8080 │ │:3306 │ │
│ └──┬───┘ └──────┘ │
└─────┼────────────────────────┘
│ SERVER_PORT 對外映射
▼
反向代理 / 負載平衡器
維運操作
常用指令
# 查看服務狀態
docker compose ps
docker compose -f docker-compose.prod.yml ps
# 查看應用程式日誌
docker compose logs -f app
docker compose -f docker-compose.prod.yml logs -f app
# 重啟應用程式(不影響資料庫)
docker compose restart app
# 停止並移除容器(保留 Volume)
docker compose down
# 停止並移除容器與 Volume(⚠️ 會刪除資料庫資料)
docker compose down -vCode language: PHP (php)
健康檢查
| 檢查項目 | 方式 |
|---|---|
| 應用程式 | curl http://localhost:8080/api/actuator/health(若已啟用)或 Swagger UI |
| 資料庫 | Compose healthcheck:mysqladmin ping |
| 容器狀態 | docker compose ps 確認 healthy / running |
滾動更新
透過一邊淘汰舊版本、一邊換上新版本達到零停機時間(Zero Downtime)
export APP_TAG=v1.0.1
docker compose -f docker-compose.prod.yml pull app
docker compose -f docker-compose.prod.yml up -d appCode language: JavaScript (javascript)
原始碼索引
| 檔案 | 職責 |
|---|---|
Dockerfile | 多階段映像建置 |
docker-compose.yml | 本地開發 Compose 定義 |
docker-compose.prod.yml | 生產部署 Compose 定義 |
.env.example | 環境變數範本 |
.dockerignore | Docker 建置排除清單 |
build.gradle | Gradle 建置與 Spotless 設定 |
application.yml | 預設應用程式設定(local Profile) |
application-server.yml | 生產環境設定(server Profile) |
how-to-start.md | 快速啟動指南 |
