Go: Docker を使った Go のデプロイ:マルチステージビルド、スクラッチイメージ、クロスコンパイル、K8s

最終更新:2026-08-26

Goの静的コンパイル機能により、コンテナ化されたデプロイに最適な言語となっています。1つのバイナリファイルとスクラッチイメージを組み合わせるだけで、12MBの本番用イメージが完成します。

Go APIを本番環境にデプロイする際、適切に設計されたDockerfileを使用すれば、ベースイメージのサイズを1.2GBから12MBまで最適化することができます。

1. 学習内容



2. バックエンドエンジニアの実話

(1) 課題:1.2 GBのDockerイメージをデプロイするのに、毎回5分かかる

ボブのeコマースAPIは、まもなく公開される予定です:

「最も手軽な方法を採用しました。ベースイメージとしてFROM golang:1.22を使用し、そこにソースコードをCOPYして、コンテナ内でコンパイルしました。イメージのサイズは1.2 GBで、デプロイのたびにプルするのに5分かかります。CI/CDパイプラインは、コミットからデプロイまで15分かかります。上司は『デプロイが遅すぎる。ロールバックするのに10分もかかる』と言いました。」

DOCKERFILE
# 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の解決策:多段階ビルド

DOCKERFILE
# 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

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"]

▶ サンプル:アルパイン版

DOCKERFILE
# ===== 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
# 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ランタイムが必要;イメージサイズが増加
🔥 よくある間違い: Go コードで 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

YAML
# 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の最小限のデプロイ

YAML
# 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
100%
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

GO 📖 参照専用
// 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)
    }
}
論理コード 56 行(40 行制限超過、参照専用)

(2) 付属の Dockerfile

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 ファイル

TEXT 📖 参照専用
# .dockerignore
.git
.gitignore
*.md
bin/
tmp/
Dockerfile
.dockerignore
💡 ヒント: .dockerignore はビルド速度に大きな影響を与えます。不要なファイルを除外することで、Docker デーモンに送信されるコンテキストのサイズを縮小します。.git ディレクトリ(数百MBに達することもあります)を追加することで、ビルドを大幅に高速化できます。


❓ よくある質問

Q なぜマルチステージビルドではイメージのサイズが小さくなるのですか?
A 第1ステージ(ビルダー)では、バイナリをコンパイルするために完全なGoイメージを使用しますが、第2ステージ(実行)では、コンパイル済みのバイナリのみを最小限のベースイメージにコピーします。ビルドツール(コンパイラ、依存関係マネージャー、ソースコード)は最終的なイメージには含まれず、バイナリ自体のみが含まれます。
Q scratchイメージは安全ですか?
A scratchは最も安全です。完全に空の状態であり、シェル、ライブラリ、ツールなどは一切含まれていません。攻撃者はコンテナ内でコマンドを実行することはできません。ただし、プログラムでSSL証明書、タイムゾーンデータ、またはシェルが必要な場合は、ビルダーステージからこれらのファイルをコピーするか、alpine/distrolessに切り替える必要があります。
Q CGO_ENABLED=0 とはどういう意味ですか?
A デフォルトでは、Go は動的リンク(CGO_ENABLED=1、C ランタイムを使用)を使用します。CGO_ENABLED=0 を設定すると、Go は外部ライブラリに依存しない完全な静的バイナリを生成します。これにより、そのバイナリは scratch を含むあらゆる Linux システム上で実行可能になります。
Q クロスコンパイルはどのように行えばよいですか?
A 環境変数 GOOS(ターゲットOS)と GOARCH(ターゲットアーキテクチャ)を設定してください。GOOS=linux GOARCH=amd64 go build では、macOS上でLinux用バイナリをコンパイルします。Goはほぼすべてのプラットフォームの組み合わせに対応しています。クロスコンパイルは、CGO_ENABLED=0 の場合が最も簡単です。
Q ヘルスチェックとレディネスプローブの違いは何ですか?
A ヘルスチェックは、プロセスが正常に動作しているかどうかを確認するものです。正常でない場合、K8sはPodを再起動します。レディネスプローブは、サービスがトラフィックを受け入れる準備ができているかどうかを確認するものです。準備ができていない場合、K8sはそのPodをサービスから削除します。起動フェーズではレディネスプローブを、実行フェーズではライブネスプローブを使用します。
Q DockerとK8sの関係はどのようなものですか?K8sを学ぶ必要はありますか?
A Dockerはコンテナランタイムであり、アプリケーションを標準化された単位にパッケージ化します。K8sはコンテナオーケストレーションプラットフォームであり、複数のコンテナのデプロイ、スケーリング、ヘルスチェックを管理します。小規模なプロジェクトではDocker(またはDocker Compose)だけで十分ですが、大規模なプロジェクトではK8sが必要となります。このコースでは、K8sのオーケストレーション事例のみを取り上げ、K8sの概念については深く掘り下げません。
Q GoのDockerイメージは通常どのくらいのサイズですか?
A Goの静的バイナリ+スクラッチ ≈ 10~20 MB。Pythonアプリケーション(python:3.12ベース) ≈ 300 MB。Node.jsアプリケーション(node:22ベース) ≈ 400 MB。Javaアプリケーション(amazoncorretto:21ベース)は約500 MBです。Goのイメージはサイズ面で大きな優位性があります。

📖 まとめ


📝 練習問題

  1. 基本 (難易度 ⭐): シンプルな Go HTTP サービス用の Dockerfile (マルチステージビルド) を作成してください。イメージをビルドし、docker run -p 8080:8080 を使用してアクセスできることを確認してください。docker images を使用して、イメージのサイズを確認してください。

  2. 上級(難易度 ⭐⭐)データベースを伴う Go アプリケーションのフルスタックコンテナ化を実装する。要件:(1) 3つのサービス:Go API、MySQL、Redis;(2) docker-compose.ymlによるオーケストレーション;(3) APIを起動する前に、データベースとRedisが利用可能であることを確認するためのヘルスチェックを実装すること;(4) Goコード内でos.Getenvを使用してデータベースの接続情報を読み取る;(5) .dockerignoreを使用して不要なファイルを除外する。

  3. 課題(難易度 ⭐⭐⭐)完全な CI/CD パイプライン(概念的な実装。実際の CI は不要)を実装してください。要件: (1) Makefileは、make build(linux/amd64 + linux/arm64のクロスコンパイル)、make docker-build、およびmake docker-プッシュに対応していること; (2) マルチステージの Dockerfile によるビルド;(3) 各ステージにおけるビルドキャッシュの最適化;(4) K8s deployment.yamlservice.yaml;(5) ライブネスプローブおよびレディネスプローブを含むこと;(6) リソース制限 (requests および limits)。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%