iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Software Development

Go Clean Architecture API 開發全攻略系列 第 17

Dockerize 你的 Go 應用:使用 Docker Compose 打造一致的開發環境

  • 分享至 

  • xImage
  •  

我們前面已經完成的基礎的架構和程式碼撰寫,現在是時候將我們的應用程式容器化,並使用 Docker Compose 來管理多個服務(應用程式、MySQL 和 Redis)。
這樣不僅能確保我們的開發環境和生產環境一致,還能大幅簡化部署流程。
在開發過程中,我們經常會遇到「在我機器上可以運行」的問題,這通常是因為環境配置不一致所導致的。
而 Docker 就是這個解決方案。

在本文中,我們將:

  1. 為我們的 Go 應用程式撰寫一個高效的 Dockerfile
  2. 撰寫一個 docker-compose.yaml 檔案來定義和管理多個服務 - for local development.
  3. 使用 Docker Compose 來啟動和管理我們的應用程式。
  4. 將 Docker 相關指令整合到 Makefile 中,簡化日常操作。

第一部分:Dockerfile - 打造精簡的 Go 映像檔

一個好的 Docker 映像檔應該是小巧且安全的。我們將使用**多階段建置(multi-stage build)**的模式來達成這個目標。
這能讓我們在一個包含完整 Go 工具鏈的「建置環境」中編譯程式,然後只將最終的執行檔複製到一個極小的「運行環境」中。

在專案根目錄下建立 Dockerfile 檔案:

# 第一階段:建置環境
# 使用官方的 Go 映像檔作為建置環境
FROM golang:1.24.6-alpine AS builder

# 設定工作目錄
WORKDIR /app

# 複製 go.mod 和 go.sum 並下載依賴
# 這樣可以利用 Docker 的層快取,只有在依賴變更時才重新下載
COPY go.mod go.sum ./
RUN go mod download

# 複製所有原始碼
COPY . .

# 編譯應用程式,並將二進位檔輸出到 /app/bin 目錄
ENV GOOS=linux
ENV CGO_ENABLED=0

RUN go build -o ./bin/server ./cmd/api

# 第二階段:運行環境
# 使用更小的映像檔作為運行環境
FROM alpine:3.21.2

# 設定工作目錄
WORKDIR /app

# 從 builder stage 複製已編譯的二進位檔
COPY --from=builder /app/bin/server .

# 複製任何必要的靜態檔案(例如資料庫遷移檔案)
COPY --from=builder /app/deployments/migrations /app/deployments/migrations

ENTRYPOINT ["./server"]

設定檔解釋

  • FROM golang:1.24.6-alpine AS builder:使用官方的 Go 映像檔作為建置環境。
  • WORKDIR /app:設定工作目錄為 /app
  • COPY go.mod go.sum ./RUN go mod download: 複製 go.modgo.sum 檔案並下載所有依賴,這樣可以利用 Docker 的層快取功能。
  • COPY . .:複製所有原始碼到容器中。
  • ENV GOOS=linuxENV CGO_ENABLED=0:設定環境變數,確保編譯的二進位檔是針對 Linux 並且是靜態連結的。
  • RUN go build -o ./bin/server ./cmd/api:編譯應用程式,並將二進位檔輸出到 /app/bin 目錄。
  • FROM alpine:3.21.2:使用更小的 Alpine 映像檔作為運行環境。
  • COPY --from=builder /app/bin/server .:從建置環境複製已編譯的二進位檔到運行環境中。
  • COPY --from=builder /app/deployments/migrations /app/deployments/migrations:複製任何必要的靜態檔案(例如資料庫遷移檔案)。
  • ENTRYPOINT ["./server"]:設定容器的入口點為我們的應用程式。

第二部分:docker-compose.yaml - 定義多個服務 - for local development

接下來,我們需要一個 docker-compose.yaml 檔案來定義和管理我們的多個服務,包括 Go 應用程式、MySQL 和 Redis。
在專案根目錄下建立 docker-compose.yaml 檔案:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: go-clean-project-app
    restart: always
    ports:
      - "${LOCAL_SERVER_PORT}:${SERVER_PORT}"
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    env_file:
      - .env

  # Database Service (MySQL)
  mysql:
    image: mysql:8.0.37
    container_name: go-clean-project-mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    ports:
      - "${LOCAL_MYSQL_PORT}:${MYSQL_PORT}"
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis Service
  redis:
    image: redis:7.2.4-alpine
    container_name: go-clean-project-redis
    restart: always
    volumes:
      - redis_data:/data
    ports:
      - "${LOCAL_REDIS_PORT}:${REDIS_PORT}"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  mysql_data:
  redis_data:

設定檔解釋

  • services.app:

    • build:
      • context: .: 指定建置上下文為目前目錄。
      • dockerfile: Dockerfile: 指定使用的 Dockerfile 為 Dockerfile
    • ports:將主機的 ${LOCAL_SERVER_PORT} 埠映射到容器的 ${SERVER_PORT} 埠,這樣可以靈活設定應用程式的埠號。並且可以避免與其他服務的埠號衝突。
    • depends_on:確保 mysqlredis 服務會比 app 服務先啟動,並且只有在它們健康檢查通過後才啟動 app 服務。
    • env_file: [.env]:指示容器從 .env 檔案中讀取環境變數。
  • services.mysql:

    • image: mysql:8.0.37:使用官方的 MySQL 8.0.37 映像檔。
    • environment: 設定 MySQL 容器需要的環境變數。
    • ports:將主機的 ${LOCAL_MYSQL_PORT} 埠映射到容器的 ${MYSQL_PORT} 埠。方便我們在本地機器上訪問 MySQL。並且可以避免與其他 mysql 服務的埠號衝突。
    • volumes: [mysql_data:/var/lib/mysql]: 這是極其重要的一步。它建立一個名為 mysql_data 的具名儲存卷,並將其掛載到容器中存放 MySQL 數據的目錄。這能確保即使容器被刪除,我們的資料庫數據也能被保留下來。
    • healthcheck:設定健康檢查,確保 MySQL 服務在啟動後能正常運行。
  • services.redis:

    • image: redis:7.2.4-alpine:使用官方的 Redis 7.2.4-alpine 映像檔。
    • ports:將主機的 ${LOCAL_REDIS_PORT} 埠映射到容器的 ${REDIS_PORT} 埠。方便我們在本地機器上訪問 Redis。並且可以避免與其他 redis 服務的埠號衝突。
    • volumes: [redis_data:/data]:建立一個名為 redis_data 的具名儲存卷,並將其掛載到容器中存放 Redis 數據的目錄。
    • healthcheck:設定健康檢查,確保 Redis 服務在啟動後能正常運行。

相信看到這裡,可能有人會疑惑,為什麼我們的 docker-compose.yaml 檔案中,MySQL 和 Redis 的環境變數是使用 ${VAR} 的語法,而不是直接寫死在檔案中?
這是因為我們希望將敏感資訊(例如資料庫密碼)和環境相關的設定(例如埠號)從程式碼中分離出來。
這樣做有幾個好處:

  1. 安全性:避免將敏感資訊直接寫在程式碼中,減少洩漏風險。
  2. 靈活性:可以根據不同的環境(開發、測試、生產)使用不同的設定,而不需要修改程式碼。
  3. 易於管理:所有的環境變數都集中在一個 .env 檔案中,方便查看和修改。

隱藏知識點:
Docker Compose 會自動尋找專案根目錄下的 .env 檔案,並將其中的變數載入到環境中。
因為這一點,所以我們可以在 docker-compose.yaml 檔案中使用 ${VAR} 的語法來引用 .env 設定的變數內容。

第三部分:使用 Docker Compose 啟動和管理應用程式

現在我們已經有了 Dockerfiledocker-compose.yaml,我們可以使用 Docker Compose 來啟動和管理我們的應用程式。
在專案根目錄下,打開終端並執行以下指令:

docker compose up -d --build

這個指令會做以下幾件事:

  1. --build:強制重新建置映像檔,即使沒有變更。
  2. -d:讓容器在背景執行。
  3. Docker Compose 會根據 docker-compose.yaml 檔案的設定,啟動所有定義的服務。
    啟動後,你可以使用以下指令來查看正在運行的容器:
docker ps

如果你需要查看某個容器的日誌,可以使用以下指令:

docker compose logs -f app

這會持續輸出 app 服務的日誌,方便你進行除錯。
如果你需要停止並移除所有容器,可以使用以下指令:

docker compose down

這會停止並移除所有由 Docker Compose 啟動的容器,但不會刪除具名儲存卷,這樣你的資料庫數據仍然會被保留。

如果你需要刪除具名儲存卷,可以使用以下指令:

docker compose down -v

這會同時刪除所有由 Docker Compose 啟動的容器和具名儲存卷,請謹慎使用。

第四部分:將 Docker 相關指令整合到 Makefile

為了讓日常操作更加方便,我們可以將常用的 Docker 指令整合到 Makefile 中。
在專案根目錄下的 Makefile 中,Development 區域加入以下內容:

## 這一行是修改原本的 .PHONY: run
.PHONY: run dockerUp dockerDown dockerDownClean dockerLogs

dockerUp: ## 啟動並建置 Docker 容器
	docker compose up -d --build

dockerDown: ## 停止並移除 Docker 容器
	docker compose down

dockerDownClean: ## 停止並移除 Docker 容器和具名儲存卷
	docker compose down -v

dockerLogs: ## 查看 app 服務的日誌
	docker compose logs -f app

這樣一來,你就可以使用以下指令來管理你的 Docker 容器:

  • 啟動並建置容器:
    make dockerUp
    
  • 停止並移除容器:
    make dockerDown
    
  • 停止並移除容器和具名儲存卷:
    make dockerDownClean
    
  • 查看 app 服務的日誌:
    make dockerLogs
    

總結

通過將我們的 Go 應用程式容器化並使用 Docker Compose來管理多個服務,我們不僅確保了開發環境和生產環境的一致性,還大幅簡化了部署流程。
這樣的設定讓我們能夠更專注於開發,而不必擔心環境配置的問題。
此外,將常用的 Docker 指令整合到 Makefile 中,進一步提升了我們的工作效率。

本文添加的完整內容可以到 Github 觀看


上一篇
Makefile 完全指南:自動化你的 Go 專案開發流程
下一篇
API 文件:使用程式產生 API 文件,讓前後端協作更順暢
系列文
Go Clean Architecture API 開發全攻略19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言