Go: Docker を使った Go のデプロイ:マルチステージビルド、スクラッチイメージ、クロスコンパイル、K8s
最終更新:2026-08-26
Goの静的コンパイル機能により、コンテナ化されたデプロイに最適な言語となっています。1つのバイナリファイルとスクラッチイメージを組み合わせるだけで、12MBの本番用イメージが完成します。
Go APIを本番環境にデプロイする際、適切に設計されたDockerfileを使用すれば、ベースイメージのサイズを1.2GBから12MBまで最適化することができます。
1. 学習内容
- 多段階ビルド
- Scratch、Alpine、またはDistrolessのイメージを選択する
CGO_ENABLED=0静的コンパイル- クロスプラットフォームのクロスコンパイル
- Dockerのヘルスチェック
- Docker Compose によるマルチサービスのオーケストレーション
- K8s デプロイメントの YAML 例
2. バックエンドエンジニアの実話
(1) 課題:1.2 GBのDockerイメージをデプロイするのに、毎回5分かかる
ボブのeコマースAPIは、まもなく公開される予定です:
「最も手軽な方法を採用しました。ベースイメージとして
FROM golang:1.22を使用し、そこにソースコードをCOPYして、コンテナ内でコンパイルしました。イメージのサイズは1.2 GBで、デプロイのたびにプルするのに5分かかります。CI/CDパイプラインは、コミットからデプロイまで15分かかります。上司は『デプロイが遅すぎる。ロールバックするのに10分もかかる』と言いました。」
# Bad approach: compile inside container, keep all build tools
FROM golang:1.22 # 800MB + compiler tools
WORKDIR /app
COPY . .
RUN go build -o server .
EXPOSE 8080
CMD ["./server"] # Image 1.2GB! Includes compiler, dependencies, toolchain
(2) Goの解決策:多段階ビルド
# Good approach: multi-stage build
# Stage 1: compile (use full Go image)
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .
# Stage 2: run (use minimal image)
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"] # Image 12MB! Only the binary
(3) 結果:最適化の前後
| メートル法 | 単段式 (Go: 1.22) | 多段式 (Scratch) | 改善度 |
|---|---|---|---|
| 画像サイズ | 1.2 GB | 12 MB | 100倍 |
| デプロイメント・プル | 5分 | 5秒 | 60回 |
| セキュリティリスク | コンパイラやツールチェーンを含む | バイナリのみ | 攻撃対象領域が最小限 |
| キャッシュの構築 | ❌ 毎回完全なコンパイル | ✅ 階層型依存関係キャッシュ | — |
3. Dockerfile のベストプラクティス
▶ サンプル:GoのマルチステージDockerfile
# ===== Stage 1: Build =====
FROM golang:1.22-alpine AS builder
# Set working directory
WORKDIR /app
# Copy dependency files first (leverage Docker cache)
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Static compilation
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .
# ===== Stage 2: Run =====
FROM scratch
# Copy binary from builder stage
COPY --from=builder /app/server /server
# If timezone files are needed
# COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# If SSL certificates are needed
# COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/server", "-health"]
CMD ["/server"]
▶ サンプル:アルパイン版
# ===== Stage 1: Build =====
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .
# ===== Stage 2: Run (Alpine) =====
FROM alpine:3.19
# Install runtime dependencies (if needed)
# RUN apk --no-cache add ca-certificates tzdata
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]
(3) ベースイメージの選択
| 画像 | サイズ | セキュリティ | 利用例 |
|---|---|---|---|
scratch |
0 MB | ✅ 攻撃対象領域が最小限 | 外部依存関係のない純粋なGoで静的コンパイル済み |
alpine |
5 MB | ⚠️ musl libc | シェル、curl、証明書などが必要です |
distroless |
20 MB | ✅ 最小構成 + ツール | SSL証明書、タイムゾーンデータが必要 |
golang:alpine |
350 MB | ❌ 開発用 | ビルドフェーズ専用 |
golang:1.22 |
800 MB | ❌ | 本番環境では絶対に使用しないでください |
-ldflags="-s -w" を使用するとバイナリのサイズを縮小できます。-s はシンボルテーブルを削除し、-w は DWARF デバッグ情報を削除します。これにより、動作に影響を与えることなく、バイナリのサイズをさらに 30~40% 縮小できます。
4. クロスコンパイル
▶ サンプル:クロスコンパイルスクリプト
# Makefile
APP=server
.PHONY: build-all
# Build for current platform
build:
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP) .
# Cross-compile for multiple platforms
build-all:
# Linux amd64 (most common)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/$(APP)-linux-amd64 .
# Linux arm64 (AWS Graviton / Apple M1)
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o bin/$(APP)-linux-arm64 .
# macOS
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o bin/$(APP)-darwin-amd64 .
# Windows
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o bin/$(APP)-windows-amd64.exe
# Docker build
docker-build:
docker build -t myapp:latest .
(2) CGO_ENABLEDの影響
| CGO_ENABLED | メリット | デメリット |
|---|---|---|
=0 |
静的コンパイル済み、あらゆるプラットフォームで動作、最小限のイメージ | Cライブラリ(SQLite用のCドライバなど)は使用不可 |
=1 (デフォルト) |
Cライブラリが利用可能 | Cランタイムが必要;イメージサイズが増加 |
mattn/go-sqlite3(C ドライバー)を使用している場合、CGO_ENABLED=0 を設定するとコンパイルに失敗します。解決策:純粋なGo製SQLiteドライバ(modernc.org/sqliteなど)を使用するか、alpineイメージでCGO_ENABLED=1を設定してください(gccとmusl-devのインストールが必要です)。
5. 複数のサービス向けの Docker Compose
▶ サンプル:EコマースAPI + データベース + Redis
# docker-compose.yml
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- DB_HOST=db
- DB_PORT=3306
- DB_USER=app
- DB_PASSWORD=secret
- DB_NAME=shop
- REDIS_ADDR=redis:6379
- GIN_MODE=release
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
healthcheck:
test: ["CMD", "/server", "-health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: shop
MYSQL_USER: app
MYSQL_PASSWORD: secret
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
volumes:
db_data:
6. K8sのデプロイ
▶ サンプル:K8sの最小限のデプロイ
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-api
labels:
app: go-api
spec:
replicas: 3
selector:
matchLabels:
app: go-api
template:
metadata:
labels:
app: go-api
spec:
containers:
- name: api
image: myregistry/go-api:latest
ports:
- containerPort: 8080
env:
- name: DB_HOST
value: "mysql-service"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: go-api-service
spec:
selector:
app: go-api
ports:
- port: 80
targetPort: 8080
type: LoadBalancer
flowchart TD
subgraph Build ["Build Phase"]
SRC[Source code] --> DEP[go mod download]
DEP --> BUILD[go build]
BUILD --> BIN[Binary 15MB]
end
subgraph Container ["Container Phase"]
BIN --> CP[COPY to scratch]
CP --> IMG[Image 12MB]
end
subgraph Deploy ["Deploy Phase"]
IMG --> PUSH[Push to Registry]
PUSH --> K8S[K8s Deployment]
K8S --> POD[Pod 3 replicas]
end
Build --> Container --> Deploy
7. 完全な例:EコマースAPIのコンテナ化プロセス全体
▶ サンプル:ヘルスチェック機能付きフルAPI
// cmd/server/main.go (complete API with health check)
package main
import (
"context"
"encoding/json"
"flag"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
healthFlag := flag.Bool("health", false, "run health check")
flag.Parse()
if *healthFlag {
// Health check mode: check if service is reachable
resp, err := http.Get("http://localhost:8080/health")
if err != nil {
os.Exit(1)
}
resp.Body.Close()
os.Exit(0)
}
mux := http.NewServeMux()
// Health check endpoint
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
mux.HandleFunc("GET /ready", func(w http.ResponseWriter, r *http.Request) {
// Check if dependencies like database are ready
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"ready": "true"})
})
// Business endpoint
mux.HandleFunc("GET /api/products", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"products": []string{"laptop", "mouse", "keyboard"},
})
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// Graceful shutdown
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Println("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(ctx)
}()
log.Println("Service listening on :8080")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}
(2) 付属の Dockerfile
# Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Static compilation + strip debug info
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server
# === Run stage ===
FROM scratch
# Copy binary
COPY --from=builder /app/server /server
# If SSL certificates are needed (accessing external HTTPS APIs)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/server", "-health"]
CMD ["/server"]
(3) .dockerignore ファイル
# .dockerignore
.git
.gitignore
*.md
bin/
tmp/
Dockerfile
.dockerignore
.dockerignore はビルド速度に大きな影響を与えます。不要なファイルを除外することで、Docker デーモンに送信されるコンテキストのサイズを縮小します。.git ディレクトリ(数百MBに達することもあります)を追加することで、ビルドを大幅に高速化できます。
❓ よくある質問
alpine/distrolessに切り替える必要があります。CGO_ENABLED=0 を設定すると、Go は外部ライブラリに依存しない完全な静的バイナリを生成します。これにより、そのバイナリは scratch を含むあらゆる Linux システム上で実行可能になります。GOOS=linux GOARCH=amd64 go build では、macOS上でLinux用バイナリをコンパイルします。Goはほぼすべてのプラットフォームの組み合わせに対応しています。クロスコンパイルは、CGO_ENABLED=0 の場合が最も簡単です。node:22ベース) ≈ 400 MB。Javaアプリケーション(amazoncorretto:21ベース)は約500 MBです。Goのイメージはサイズ面で大きな優位性があります。📖 まとめ
- マルチステージビルド:「ビルダー」ステージでのコンパイル、「ラン」ステージでの最小イメージの生成
- ベースイメージ:scratch (minimal) > distroless > alpine > golang:alpine
CGO_ENABLED=0: 完全に静的コンパイルされ、どのプラットフォームでも動作する-ldflags="-s -w": シンボルテーブルとデバッグ情報を削除する- クロスコンパイル:
GOOS=linux GOARCH=arm64 go build HEALTHCHECK: Dockerのネイティブヘルスチェックdocker-compose: マルチサービス・オーケストレーション(API + DB + Redis)- K8sのデプロイ:最小限のYAML例(ライヴネス・プローブとレディネス・プローブ)
.dockerignore: 不要なファイルを除外してビルドを高速化する
📝 練習問題
-
基本 (難易度 ⭐): シンプルな Go HTTP サービス用の Dockerfile (マルチステージビルド) を作成してください。イメージをビルドし、
docker run -p 8080:8080を使用してアクセスできることを確認してください。docker imagesを使用して、イメージのサイズを確認してください。 -
上級(難易度 ⭐⭐):データベースを伴う Go アプリケーションのフルスタックコンテナ化を実装する。要件:(1) 3つのサービス:Go API、MySQL、Redis;(2) docker-compose.ymlによるオーケストレーション;(3) APIを起動する前に、データベースとRedisが利用可能であることを確認するためのヘルスチェックを実装すること;(4) Goコード内で
os.Getenvを使用してデータベースの接続情報を読み取る;(5).dockerignoreを使用して不要なファイルを除外する。 -
課題(難易度 ⭐⭐⭐):完全な CI/CD パイプライン(概念的な実装。実際の CI は不要)を実装してください。要件: (1) Makefileは、
make build(linux/amd64 + linux/arm64のクロスコンパイル)、make docker-build、およびmake docker-プッシュに対応していること; (2) マルチステージの Dockerfile によるビルド;(3) 各ステージにおけるビルドキャッシュの最適化;(4) K8sdeployment.yaml+service.yaml;(5) ライブネスプローブおよびレディネスプローブを含むこと;(6) リソース制限 (requestsおよびlimits)。