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 使用者執行]

建置

  1. 複製 Gradle Wrapper、build.gradlesettings.gradle、子模組
  2. 預先下載依賴(獨立 Layer,利於 Docker Cache)
  3. 複製 src/ 並執行 ./gradlew build -x test -x spotlessCheck
  4. 產出 build/libs/*.jar

執行

  1. 使用較小的 JRE 映像(25-jre-alpine
  2. 建立非 root 使用者 spring
  3. 複製 JAR 為 /app/app.jar
  4. 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 熱重載遠端除錯

服務映像 / 建置說明
dbmysql:8.4資料庫,含 healthcheck
redisredis:latest快取服務
app本地 Dockerfiletarget: buildergradlew bootRun 啟動,掛載原始碼
admineradminer: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 拉取已建置好的映像。

服務映像說明
dbmysql:8.4資料庫,無對外 Port(僅內部網路)
appharbor.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_PASSWORDMySQL root 密碼
DB_DATABASE資料庫名稱campus-activity
DB_USER應用程式 DB 帳號campus-activity_user
DB_PASSWORD應用程式 DB 密碼
DB_PORTMySQL 對外 Port3306
SPRING_JPA_HIBERNATE_DDL_AUTOHibernate DDL 策略update
SPRING_APP_UPLOAD_PATH檔案上傳路徑/app/upload
SPRING_SERVER_HOST應用程式 Hosthttp://localhost
SPRING_SERVER_PORT應用程式 Port8080
SPRING_DEBUG_PORTJDWP 除錯 Port5005
TIMEZONE時區Asia/Taipei

生產環境

docker-compose.prod.yml 額外需求

變數說明
APP_TAGHarbor 映像標籤(如 latestv1.0.0、commit SHA)
SPRING_PROFILES_ACTIVESpring 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 bootRunjava -jar app.jar
映像來源本地建置(builder stage)Harbor 遠端映像
原始碼掛載是(熱重載)
除錯埠開放 5005不開放
Redis無(需另行規劃)
Adminer
DDL 策略updatevalidate
Spring Profilelocal(預設)server
Swagger UI啟用預設關閉
DB 對外 Port開放 3306不對外暴露
執行使用者root(builder 內)spring(非 root)

CI/CD 流程(建議)

目前儲存庫尚未納入 .gitlab-ci.yml。依專案使用的 GitLabHarbor 基礎設施,建議採用以下 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-builddocker build 產出映像
docker-push推送至 harbor.ntubimdbirc.tw/campus-activity/campus-activity_app
deploySSH 或 Docker Compose 更新遠端服務

映像標籤

標籤時機
{commit-sha}每次 Pipeline 建置(可追溯)
devdevelop 分支最新版
latestmain 分支最新版
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_USERHarbor 登入帳號
HARBOR_PASSWORDHarbor 登入密碼
PROD_SERVER生產伺服器 IP 或 Hostname
SSH_PRIVATE_KEY部署用 SSH 金鑰

生產 Profile 設定重點

啟用 server Profile 時,載入 application-server.yml

項目設定
Hibernate DDLvalidate(不自動變更 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環境變數範本
.dockerignoreDocker 建置排除清單
build.gradleGradle 建置與 Spotless 設定
application.yml預設應用程式設定(local Profile)
application-server.yml生產環境設定(server Profile)
how-to-start.md快速啟動指南