本文 AI 產出,尚未審核

Golang 專案實戰與部署 ── Docker 容器化

簡介

在現代軟體開發流程中,Docker 已成為最常見的部署工具之一。它能把應用程式、相依套件、作業系統環境全部封裝成一個可搬移的容器,讓「在我機器上跑得好」的問題徹底消失。對於使用 Golang 開發的服務,Docker 更能發揮其輕量、啟動快的特性,讓開發、測試、上線的流程變得一致且自動化。

本篇文章將從 Docker 基礎概念實作範例常見陷阱與最佳實踐,一路帶領讀者完成一個簡單的 Go Web API 的容器化,並說明在實務專案中如何運用 Docker 進行部署與擴充。


核心概念

1. Docker 什麼是容器?

Docker 容器是一種 輕量級、可執行的軟體包,內含程式碼、執行時、系統工具與相依套件。與傳統的虛擬機不同,容器共享宿主機的核心 (kernel),因此啟動時間通常在 毫秒級,資源開銷也相對較低。

重點:容器的可移植性來源於 Docker Image(鏡像檔),而 Dockerfile 則是描述如何建構這個鏡像的腳本。

2. Dockerfile 基本結構

指令 說明
FROM 指定基礎映像,例如 golang:1.22-alpine
WORKDIR 設定工作目錄
COPY / ADD 複製檔案到容器內
RUN 在建構階段執行指令(如安裝套件)
ENV 設定環境變數
EXPOSE 宣告容器對外的埠號
CMD / ENTRYPOINT 容器啟動時執行的指令

3. 多階段建構(Multi‑Stage Build)

Go 程式編譯後的執行檔通常只有幾 MB,但若直接以 golang 官方映像作為基礎,最終鏡像會帶有整個編譯環境,體積會變大。多階段建構 允許我們在同一個 Dockerfile 中使用兩個(或以上)FROM,先在第一階段編譯,然後把產出的二進位檔拷貝到第二階段的精簡映像(如 scratchalpine),大幅減少鏡像大小。

4. Docker Compose

在實務上,一個服務往往不只單一容器,還可能需要資料庫、快取、訊息佇列等。Docker Compose 透過 docker-compose.yml 定義多個服務的關係、網路、環境變數等,讓 一鍵啟動 成為可能。


程式碼範例

以下示範一個簡單的 Go RESTful API,並以 Docker 完成容器化。每段程式碼均附有說明,方便初學者快速掌握。

1. 建立 Go 專案

mkdir go-docker-demo
cd go-docker-demo
go mod init github.com/example/go-docker-demo

main.go

package main

import (
	"encoding/json"
	"log"
	"net/http"
)

// User 表示簡易的使用者資料結構
type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// helloHandler 回傳一段文字
func helloHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, Docker!"))
}

// usersHandler 回傳 JSON 陣列
func usersHandler(w http.ResponseWriter, r *http.Request) {
	users := []User{
		{ID: 1, Name: "Alice"},
		{ID: 2, Name: "Bob"},
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(users)
}

func main() {
	http.HandleFunc("/", helloHandler)
	http.HandleFunc("/users", usersHandler)

	// 監聽 8080 埠
	log.Println("Server listening on :8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatalf("Server error: %v", err)
	}
}

說明:此程式僅使用標準函式庫,無需額外相依,適合作為 Docker 教學的基礎範例。

2. Dockerfile(使用多階段建構)

# ---------- Build Stage ----------
FROM golang:1.22-alpine AS builder

# 安裝 git(若需要拉取私有模組)
RUN apk add --no-cache git

WORKDIR /app

# 先複製 go.mod、go.sum 以利用快取
COPY go.mod go.sum ./
RUN go mod download

# 複製所有原始碼
COPY . .

# 編譯成靜態二進位檔
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /go-docker-demo .

# ---------- Runtime Stage ----------
FROM alpine:3.20

# 建立非 root 使用者,提升安全性
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

WORKDIR /app

# 從 builder 階段拷貝二進位檔
COPY --from=builder /go-docker-demo .

# 暴露容器內部埠號
EXPOSE 8080

# 容器啟動指令
ENTRYPOINT ["./go-docker-demo"]

重點

  • CGO_ENABLED=0 產生純靜態二進位檔,讓最終映像可在 scratchalpine 中執行。
  • 使用 alpine 作為 Runtime Stage,可把最終鏡像大小控制在 約 10 MB 左右。
  • 建立非 root 使用者 appuser,減少安全風險。

3. 建置與測試 Docker 映像

# 建置映像,標記為 go-docker-demo:latest
docker build -t go-docker-demo:latest .

# 以本機 8080 埠對映容器 8080 埠,測試服務是否正常
docker run --rm -p 8080:8080 go-docker-demo:latest

在瀏覽器或使用 curl 測試:

curl http://localhost:8080/
# => Hello, Docker!

curl http://localhost:8080/users
# => [{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]

4. Docker Compose 範例(加入 PostgreSQL)

version: "3.9"
services:
  api:
    build: .
    container_name: go_api
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
      - DB_USER=postgres
      - DB_PASSWORD=secret
      - DB_NAME=demo
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    container_name: pg_demo
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: demo
    volumes:
      - pg_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  pg_data:

說明

  • depends_on 確保 API 服務在資料庫容器啟動後才執行。
  • 使用 volumes 持久化 PostgreSQL 資料,避免容器重啟時資料遺失。
  • 只要在專案根目錄執行 docker-compose up --build,整個環境即會一次性啟動。

5. 在 CI/CD 中使用 Docker(GitHub Actions 範例)

name: CI

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Build Docker image
        run: |
          docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
          echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

重點:透過 CI 流程自動建置、推送 Docker 映像至容器註冊表,讓部署環境只需要拉取映像即可。


常見陷阱與最佳實踐

陷阱 可能的影響 解決方案 / 最佳實踐
使用 latest 標籤 部署時不易追蹤版本,升級可能導致不可預期的錯誤 固定版本號(如 v1.0.3)或使用 Git commit SHA 作為標籤
容器內部以 root 身份執行 安全風險,若容器被入侵,攻擊者可取得宿主機權限 建立非 root 使用者,並在 Dockerfile 中使用 USER 指令
未使用 .dockerignore 建置時會把整個專案目錄(包括 .git、測試檔等)複製進容器,導致鏡像過大 在專案根目錄建立 .dockerignore,排除不必要的檔案
過度依賴 ADD ADD 會自動解壓縮檔案,可能產生意外行為 盡量使用 COPY,僅在需要解壓縮或下載遠端檔案時才使用 ADD
忽略健康檢查 (HEALTHCHECK) 容器崩潰但仍被認為是「Running」狀態,導致服務不可用 在 Dockerfile 中加入 HEALTHCHECK,或在 docker-compose.yml 使用 healthcheck 設定
未設定資源限制 容器可能耗盡宿主機資源(CPU、Memory) 使用 docker run --memory--cpus 或在 Compose 中設定 deploy.resources

最佳實踐小結

  1. 最小化映像:使用多階段建構、scratchalpine
  2. 環境變數管理:將機密資訊(DB 密碼、API 金鑰)放在 Docker secret環境變數檔,避免硬編碼。
  3. 日誌輸出:容器內部的日誌應直接寫到 stdout/stderr,讓 Docker 叢集(如 Kubernetes)可以集中收集。
  4. 版本化:每次建置都使用唯一標籤,配合 CI/CD 讓部署可回溯。
  5. 測試:在建置階段加入單元測試或整合測試,確保映像在推送前已通過驗證。

實際應用場景

場景 為何使用 Docker 具體做法
微服務架構 每個服務獨立部署、升級、擴容 為每個 Go 微服務建立獨立 Dockerfile,使用 Docker Compose 或 Kubernetes 管理
本地開發環境 團隊成員環境一致,避免「在我電腦上可以跑」的問題 提供 docker-compose.yml,一條指令 docker-compose up -d 即可啟動所有依賴(DB、Cache)
CI/CD 自動化 Build → Test → Deploy 全程自動化 在 GitHub Actions、GitLab CI 中使用 Docker BuildKit,推送至私有 Registry,最後在 Kubernetes 叢集滾動更新
資源受限的 Edge 裝置 Go 程式本身執行效能佳,Docker 提供輕量化封裝 使用 scratch 映像,僅保留二進位檔與必要的 CA 證書,映像大小可低於 5 MB
多租戶 SaaS 平台 每個租戶需要隔離的執行環境 為每個租戶建立獨立容器,配合 Docker Swarm 或 Nomad 的資源配額功能

總結

Docker 為 Golang 應用提供了 可移植、輕量、快速部署 的解決方案。透過 多階段建構 可以將編譯環境與執行環境徹底分離,讓最終映像保持極小;配合 Docker ComposeKubernetes,即使是複雜的微服務系統也能以 宣告式 的方式管理。

在實務開發中,務必遵守 安全性(非 root、環境變數管理)與 可維護性(版本標籤、健康檢查、資源限制)的最佳實踐,才能讓 Docker 成為提升開發效率與部署可靠性的利器。

最後提醒:容器化不是一次性的工作,而是一個 持續迭代 的過程。隨著專案規模成長,持續優化 Dockerfile、調整資源配置,並將容器安全掃描納入 CI 流程,才能真正發揮 Docker 與 Go 的威力,打造穩定、可擴充的雲端服務。