個人的備忘録：Golangアプリのマルチステージビルドを活用した、マイグレーション対応の効率的なDockerfileについて解説してみた

## はじめに
GolangアプリケーションをDockerで運用する際、適切なDockerfileの設計はパフォーマンスやセキュリティに大きく影響します。  

本記事では、**マルチステージビルドを活用した効率的なDockerfile** について解説し、**コンテナサイズの削減や最適化のポイント** を紹介します。

## 書こうと思ったきっかけ
GolangアプリケーションのDocker化を行う中で、**ビルド環境の分離やコンテナサイズの最適化が重要** だと感じました。  

特に、**シンプルかつ軽量な構成を実現するためのポイント** を整理し、他の開発者にも役立つ情報を共有したいと思い、本記事を書くことにしました。

**実際に作成したDockerfileについて**

https://github.com/free-honda/skill-typing-back-Migration/blob/development/Dockerfile.prod

## 実際のDockerfileについて

このDockerfileは、Golangアプリケーションを効率的にコンテナ化するためのものです。

```dockerfile
FROM golang:1.23.5-alpine3.21 AS builder
WORKDIR /usr/src/app

# go.mod と go.sum を先にコピーし、依存関係をダウンロード
COPY go.mod go.sum ./
RUN go mod download

# アプリケーションのソースコードをコピー
COPY . .

# メインアプリケーションのビルド
RUN CGO_ENABLED=0 GOOS=linux go build -o server .

# マイグレーション用のバイナリを作成
RUN CGO_ENABLED=0 GOOS=linux go build -o migrate_app migrate/migrate.go

# 軽量なAlpineベースのイメージを使用
FROM alpine:3.21
WORKDIR /app

# 必要なバイナリをコピー
COPY --from=builder /usr/src/app/server .
COPY --from=builder /usr/src/app/migrate_app .

# マイグレーションを実行してからアプリケーションを起動
CMD ["/bin/sh", "-c", "/app/migrate_app && /app/server"]
```

マルチステージビルドを利用して、軽量なコンテナイメージを作成し、アプリケーションのパフォーマンスやセキュリティを向上させています。

## 1. ビルドステージ (builder)

まず、Golangの公式イメージ（Alpine Linuxベース）を使い、ビルド用の環境を構築します。

```dockerfile
FROM golang:1.23.5-alpine3.21 AS builder
WORKDIR /usr/src/app
```

- `golang:1.23.5-alpine3.21` を使用して、軽量なビルド環境を用意
- `WORKDIR /usr/src/app` で作業ディレクトリを `/usr/src/app` に設定

次に、依存関係のインストールを行います。

```dockerfile
COPY go.mod go.sum ./
RUN go mod download
```

- `go.mod` と `go.sum` をコピーし、`go mod download` を実行
- これにより、依存パッケージを事前にダウンロードし、キャッシュを活用して不要な再インストールを防ぐ

続いて、アプリケーションのソースコードをコピーし、ビルドを行います。

```dockerfile
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .
```

- `CGO_ENABLED=0` を設定して、C言語の依存を無効化し、静的リンクされたバイナリを作成
- `GOOS=linux` を指定し、Linux向けのバイナリを生成
- `-o server` で出力ファイル名を `server` に指定

また、データベースマイグレーション用のバイナリも作成します。

```dockerfile
RUN CGO_ENABLED=0 GOOS=linux go build -o migrate_app migrate/migrate.go
```

- `migrate/migrate.go` をビルドし、マイグレーション実行用の `migrate_app` バイナリを作成

この段階で、アプリケーション本体 (`server`) と、マイグレーションツール (`migrate_app`) の2つのバイナリが完成しました。

## 2. ランタイムステージ

アプリケーションの実行環境として、軽量な `Alpine` イメージを使います。

```dockerfile
FROM alpine:3.21
WORKDIR /app
```

- `Alpine Linux 3.21` をベースイメージとして使用し、実行環境を構築
- `WORKDIR /app` で作業ディレクトリを `/app` に設定

次に、ビルドステージで作成したバイナリをコピーします。

```dockerfile
COPY --from=builder /usr/src/app/server .
COPY --from=builder /usr/src/app/migrate_app .
```

- `COPY --from=builder` により、ビルド済みのバイナリ (`server` と `migrate_app`) を取得し、実行環境へコピー
- Golangの開発環境 (約1GB) を削減し、数MBの軽量なコンテナを実現

最後に、マイグレーションを実行してからアプリケーションを起動するよう設定します。

```dockerfile
CMD ["/bin/sh", "-c", "/app/migrate_app && /app/server"]
```

- `/app/migrate_app` を実行し、データベースのマイグレーションを適用
- マイグレーションが完了したら `/app/server` を起動

## 3. 最適化ポイント

このDockerfileには、パフォーマンス向上やイメージの軽量化を考慮した工夫がいくつか含まれています。

### マルチステージビルドの活用

- Golangのビルド環境 (`golang:1.23.5-alpine3.21`) はサイズが大きいため、本番環境に不要なものは含めず、軽量な `Alpine` イメージに最小限のバイナリのみを配置
- これにより、最終的なコンテナサイズを数MB程度に抑えられる

### 依存関係のキャッシュを活用

- `go.mod` を先にコピーし、`go mod download` を実行することで、ソースコードの変更がない限り、依存関係の再インストールを回避

### CGO無効化 & 静的リンク

- `CGO_ENABLED=0` を設定することで、C言語の依存をなくし、シンプルなスタンドアロンバイナリを作成
- `GOOS=linux` を指定することで、Linux環境でも動作するバイナリを生成

### 軽量なAlpineイメージの採用

- `Alpine Linux` は、公式のGolangイメージよりも数百MBも軽量
- 余分なパッケージがなく、セキュリティリスクも低減

## まとめ

このDockerfileでは、Golangアプリケーションのビルドとデプロイの最適化を行っています。特に、以下のポイントが重要です。

- マルチステージビルドにより、ビルド環境を分離し、最終的なコンテナを軽量化
- `go.mod` を先にコピーし、キャッシュを活用してビルドの高速化
- `CGO_ENABLED=0` による静的バイナリ化で、余計なライブラリを削減
- `Alpine Linux` を利用して、最小限のランタイム環境を構築

このような工夫を取り入れることで、効率的でセキュアなDockerコンテナを作成できます。

***Golangアプリケーションをコンテナ化する際の参考にしてください。***