iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Build on Google AI

單鐵的人生如履薄冰!AI 教練 APP 30天開發旅程,你說能走到最後嗎?系列 第 11

Day 11 | 容器化實戰:撰寫 Dockerfile,將 Spring Boot 應用程式輕量打包

  • 分享至 

  • xImage
  •  

前言:告別「在我的電腦上明明可以跑」

經過前 10 天的奮戰,AI 教練 KAKERU 的 API 已經具備了邏輯、記憶力與防禦機制。本地端開發也算告一段落!

在準備將程式碼部署到雲端之前,我們必須解決一項問題:『在我的電腦上跑得好好的,怎麼一部署就掛了?』
就好像常聽到:『在你們這邊 WiFi 都可以連,為什麼回家就連不到。』

為了解決實際使用跟測試的作業系統與環境差異,今天我們要撰寫 Dockerfile,將這套 Java 21 + Spring Boot 應用程式打包成一個隨處可執行的 Docker Image。

你可以把『在本地建立與執行 Docker』,當作是『上雲端的模擬』。 透過 Docker,我們在自己的電腦裡切出一塊與 GCP 雲端一模一樣的微型環境。如果我們的程式碼能在本地的 Docker 容器裡存活並順利連線,那麼未來將它推上 Cloud Run 時,就能減少一些問題發生!而且,我們還要透過 Multi-stage Build (多階段建置) 的方式,把它做得輕量與安全!

前置準備:下載與安裝 Docker

在開始撰寫 Dockerfile 之前,電腦必須先安裝並啟動 Docker 引擎:

  1. 下載 Docker Desktop
    • 前往 Docker 官方網站 下載對應作業系統的安裝檔(Mac 晶片請選擇 Apple Silicon / Intel 選擇 Intel Chip)。
  2. 啟動 Docker
    • 安裝完成後開啟 Docker Desktop 應用程式,確認畫面左下角狀態顯示為「Engine running」(綠燈)。
  3. 驗證安裝成功: 開啟終端機(Terminal)輸入以下指令,確認能正確輸出版本號:
docker --version

觀念解說:為什麼需要 Multi-stage Build (多階段建置)?

傳統的 Spring Boot 容器化是本地端先下指令 mvn clean package 產出 .jar 檔,然後複製進 Image 裡。但這種做法有環境依賴與 Image 肥大的缺點。

Multi-stage Build 能夠解決這個問題,在同一個 Dockerfile 定義兩個階段:

  1. Stage 1 (編譯期 Builder):使用包含 Maven 與 JDK 21 的完整環境來編譯原始碼,產出 .jar
  2. Stage 2 (執行期 Runtime):使用極輕量的 JRE (Java Runtime Environment) Image,只把第一階段產出的 .jar 複製過來執行。

這樣最終上雲端的 Image 裡面沒有原始碼、沒有 Maven,體積最小、安全性最高!

動手實作 1:撰寫極致且相容 Google Cloud 的 Dockerfile

要寫出好的 Multi-stage Dockerfile 需要考慮很多底層細節(例如基礎 Image 選擇、使用者權限設定等)。同樣也可以與AI協作,直接將需求轉化為Prompt:

我有一個 Java 21 + Spring Boot 3 專案,準備部署到 Google Cloud Cloud Run。請幫我寫一個 Best Practice 的 Dockerfile。
要求:

  1. 使用 Multi-stage build 進行 Maven 打包。
  2. 最終 image 要極度輕量且安全 (建立並使用非 root 的 appuser 執行)。
  3. 底層 JRE 必須相容 gRPC / Netty (因為專案會連線 Firestore)。」

產生的 Dockerfile如下:

# ==========================================
# Stage 1: Build (編譯階段)
# ==========================================
# 使用官方含有 Maven 與 Java 21 的完整環境作為編譯基底
FROM maven:3.9.6-eclipse-temurin-21 AS builder

# 設定容器內的工作目錄
WORKDIR /app

# 先複製 pom.xml 並下載依賴,善用 Docker Layer Cache
COPY pom.xml .
RUN mvn dependency:go-offline

# 複製所有原始碼進去
COPY src ./src

# 執行打包指令,跳過測試以加速編譯
RUN mvn clean package -DskipTests

# ==========================================
# Stage 2: Run (執行階段)
# ==========================================
# 使用相容 gRPC / Netty Native 的 Java 21 JRE 環境 (Debian/glibc 基礎)
FROM eclipse-temurin:21-jre

# 建立非 root 系統使用者以符合最佳資安規範 (Least Privilege)
RUN groupadd -r appgroup && useradd -r -g appgroup appuser

WORKDIR /app

# 從第一階段 (builder) 將編譯好的 fat jar 複製到最終的 Image 裡
COPY --from=builder /app/target/*.jar app.jar

# 變更檔案擁有者為 appuser
RUN chown -R appuser:appgroup /app

# 切換使用非 root 使用者執行
USER appuser

# 暴露 Spring Boot 預設的 8080 Port
EXPOSE 8080

# 設定容器啟動時執行的指令
ENTRYPOINT ["java", "-jar", "app.jar"]

動手實作 2:建立 .dockerignore 避免雷隊友

在根目錄建立 .dockerignore 檔案,避免打包暫存與敏感設定:

target/
!.mvn/wrapper/maven-wrapper.jar
!target/*.jar
*.class
.idea/
.vscode/
*.log
.git/
.gitignore
README.md

動手實作 3:本地端打包指令

開啟終端機,執行以下指令進行打包(標記名稱為 kakeru-ai:1.0.0):

docker build -t kakeru-ai:1.0.0 -t kakeru-ai:latest .

看著 Multi-stage 一步步下載依賴並打包,最後產出的 Image 體積通常相對較小。
接下來:如何把這個 Container 跑起來,並且讓它有權限連線 Firestore?

如果在本地直接執行,程式一啟動就會因為找不到 Google Cloud 權限而崩潰。由於我們的 Docker 使用了安全的「非 root 使用者 (appuser)」,權限路徑的映射非常容易寫錯,所以詢問 AI:

我要在本地端用docker run 啟動剛剛包好的 Image。但我本機有設定 Google Cloud ADC 憑證。該如何把這個憑證掛載到容器內,讓以 appuser (非 root) 執行的 Spring Boot 可以順利讀取到權限?

產生對應的指令:

docker run -d --name my-kakeru-ai \
  -p 8080:8080 \
  -v ~/.config/gcloud:/home/appuser/.config/gcloud:ro \
  -e GOOGLE_APPLICATION_CREDENTIALS=/home/appuser/.config/gcloud/application_default_credentials.json \
  kakeru-ai:1.0.0

並可以使用以下指令進行發送 API 測試與日誌檢視,並將curl的內容import到 API 測試工具做測試:

# 查看容器即時日誌
docker logs -f my-kakeru-ai
# 發送測試請求
curl -X POST http://localhost:8080/apiUrl \
  -H "Content-Type: application/json" \
  -d '{
    "targetRace": "半程馬拉松",
    "currentLevel": "目前一週可以跑 3 天,四個月後想挑戰半馬"
  }'

踩坑與除錯筆記

在測試過程中,會遇到這個報錯:
Error response from daemon: Conflict. The container name "/my-kakeru-ai" is already in use.

把錯誤訊息丟給可以看出,這個container已經存在,如果同名的容器已經存在(即便它是停止狀態),就會引發名稱衝突。可以透過以下四個生命週期指令來管理你的 Docker:

  • docker run:建立並啟動一個全新容器(注意:若同名的容器已存在但處於停止狀態,直接執行 docker run 會出現名稱衝突錯誤)。
  • docker stop my-kakeru-ai:暫停/關閉容器運行。
  • docker start my-kakeru-ai:重新啟動已暫停的現有容器(會保留原本設定好的 Port 與憑證掛載)。
  • docker rm my-kakeru-ai:刪除已關閉的容器。

今日總結與明日預告

今天在與 AI 的協作下,成功利用 Docker 的 Multi-stage Build,將原本龐大的 Java 專案,濃縮成一個輕量、乾淨且安全的 Docker Image,完成了上雲端前的最後一塊拼圖。

接下來將在明天(Day 12)把這個 Image 推送到 Google Cloud Artifact Registry,並一鍵部署到 Cloud Run 上,讓 KAKERU 的API 正式在網際網路上誕生,敬請期待!


上一篇
Day 10 | 錯誤處理與防禦性設計:API 重試策略、AI 護欄與實戰除錯全紀錄
下一篇
Day 12 | 把 Docker Image 戴上手銬!部署 Cloud Run 與安全金鑰管理
系列文
單鐵的人生如履薄冰!AI 教練 APP 30天開發旅程,你說能走到最後嗎?13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言