こんにちは!Pep Upモバイルアプリチームの楊です。
今年、JMDCではアドベントカレンダーに参加しています。
本記事は、JMDC Advent Calendar 2025 9日目の記事です。
はじめに
皆さんは、GitHub ActionsでGoプロジェクトをビルドする際に、ビルド時間が長すぎて困った経験はありませんか?
直近担当していたGoのプロジェクトではGitHub Actionsでのデプロイに約58分もかかっていました。ランチを食べて戻ってきても、まだビルドが終わっていない...そんな状況でした。私たちは様々な最適化手法を実施し、とうとうデプロ時間を2分(97%削減)短縮できました!
この記事では、私たちがどのようにしてデプロイプロセスを最適化し、その過程で学んだDocker/Github Actions/Go buildの最適化テクニックを共有します。
目次
問題分析
初期状態
プロジェクトは以下のような構成でした:
プロジェクト構成: - Go 1.25.3 - 依存関係: 144個のモジュール - API: メインはGraphQL(一部はRESTful) - コードの自動生成: GraphQL (gqlgen) 約12,000行 - Database: PostgreSQL、テストはSQLite - AWS SDK, GORM, Gin, Redisなどのフレームワークを使用
最初GitHub Actionsのログを分析したところ、一番時間がかかったのはgo buildでした:
Deploy backend app ├─ Total: 58m 17s └─ #16 [builder 7/7] RUN go build: 約55分
手軽にかつ効果もでやすいことで、最初に実施したのはDockerビルドキャッシュのGithubへの外だしでした。
Workflow最適化
実施した改善策:
- BuildKitキャッシュマウント導入
- buildkit-cache-dance統合
- GitHub Actionsキャッシュ活用
# .github/workflows/deploy-reusable.yml jobs: deploy: runs-on: ubuntu-latest steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Go Build Cache for Docker uses: actions/cache@v4 with: path: | go-mod-cache go-build-cache key: ${{ runner.os }}-go${{ steps.go-version.outputs.go_version }}-arm64-build-${{ hashFiles('**/go.sum') }} - name: Inject go-build-cache uses: reproducible-containers/buildkit-cache-dance@v3 - name: Deploy to ECS with deploy script env: GO_BUILD_PARALLELISM: 8 run: ./deploy-ecs.sh main
buildkit-cache-danceの役割
GitHub ActionsのキャッシュとDockerのキャッシュマウントを橋渡し:
GitHub Actions Cache
↓ (export)
go-mod-cache/
go-build-cache/
↓ (inject)
Docker Build Cache
↓ (mount)
/root/.cache/go-build
/root/.cache/go-mod
Dockerビルドキャッシュ戦略実施後:
Deploy backend app ├─ Total: 28m 51s └─ #16 [builder 7/7] RUN go build: 約28分
改善率: 約50%削減 (58分 → 28分)
しかし、28分でもまだ長すぎます。さらに短縮できないか色々改善策を実施しました。
goビルド時間が長い主な原因
以下の4点が考えられます:
- 並列度が低かった:
-p 4では限られたCPUリソースを活かせていない - 不要な依存関係: テスト専用のSQLiteをビルドに含めていた
- 最適化不足: キャッシュ戦略やコンパイラフラグが最適化されていない
- ブランクインポートの乱用: gqlgen生成コード用の依存が本番ビルドに混入
私たちはこの4つを改善すべくさらなる高速化に取り込みました。
最適化の全体像

go buildの最適化
| カテゴリ | 最適化項目 | 実際の効果 |
|---|---|---|
| 依存関係 | SQLite依存削除 | CGO無効化により並列度向上 |
| 依存関係 | 不要なブランクインポート削除 (5個) | パッケージダウンロード・ビルド削減 |
| 並列化 | GOMAXPROCS=8設定 | ランタイム並列化 |
| キャッシュ | トリプルキャッシュマウント | ディスクI/O削減 |
| コンパイラ | -mod=readonly | キャッシュヒット率向上 |
| ビルドフラグ | 静的バイナリ化 | イメージサイズ削減 |
技術的な実装
1. 不要な依存関係の削除
ブランクインポートの乱用
main.goに以下のような不要なインポートがありました:
// ❌ Before: gqlgen生成コード専用の依存を本番コードに含めていた package main import ( "log" "sync" _ "github.com/russross/blackfriday/v2" _ "github.com/urfave/cli/v2" _ "github.com/xrash/smetrics" _ "golang.org/x/tools/go/ast/astutil" _ "golang.org/x/tools/go/packages" _ "golang.org/x/tools/imports" "app/config" "app/jobs" "app/routes" "fmt" )
これらはgqlgenの生成コードでのみ必要なもので、実行時には完全に不要です。
// ✅ After: 不要なインポートを削除 package main import ( "log" "sync" "app/config" "app/jobs" "app/routes" "fmt" )
依存関係掃除:
$ go mod tidy
結果
5個の直接依存関係と1個の間接依存関係が削除されました:
github.com/russross/blackfriday/v2github.com/urfave/cli/v2github.com/xrash/smetricsgithub.com/cpuguy83/go-md2man/v2
2. SQLite依存の削除
// config/database.go func ConnectTestDatabase() { var err error DB, err = gorm.Open(sqlite.Open("file:test.db?cache=shared&mode=memory"), &gorm.Config{}) // ... }
SQLiteはテスト専用で、本番環境ではPostgreSQLのみ使用していました。
解決策
Dockerfile.cmdから以下を削除:
# ❌ Before RUN apk update && apk add --no-cache gcc musl-dev sqlite sqlite-dev ENV CGO_ENABLED=1 # ✅ After # No build dependencies needed for CGO_ENABLED=0 ENV CGO_ENABLED=0
効果:
- ビルド時間: 10-20秒削減
- イメージサイズ: 約5-10MB削減
- gcc/musl-devのインストール不要
3. GOMAXPROCS最適化
Goランタイムの並列処理
GOMAXPROCSは、Goランタイムが同時に実行できるOSスレッド数を制御します。
# ✅ Dockerfile.main / Dockerfile.cmd ARG GO_BUILD_PARALLELISM=8 RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/root/.cache/go-mod \ GOMAXPROCS=${GO_BUILD_PARALLELISM} \ CGO_ENABLED=0 \ go build -p ${GO_BUILD_PARALLELISM} \ -o main .
GOMAXPROCSについて
CPU利用率の比較: Before (GOMAXPROCS未設定): CPU: ████░░░░ (約50%利用) After (GOMAXPROCS=8): CPU: ████████ (約80-90%利用)
期待効果: 10-20%の高速化
4. トリプルキャッシュマウント戦略
BuildKitのキャッシュマウント
Docker BuildKitの--mount=type=cacheを使用して、3つのキャッシュディレクトリをマウント:
RUN --mount=type=cache,target=/root/.cache/go-build \ # ① --mount=type=cache,target=/root/.cache/go-mod \ # ② --mount=type=cache,target=/go/pkg/mod \ # ③ GOCACHE=/root/.cache/go-build \ GOMODCACHE=/root/.cache/go-mod \ go build -o main .
各キャッシュの役割
| キャッシュ | パス | 用途 | 効果 |
|---|---|---|---|
| ① Build Cache | /root/.cache/go-build |
コンパイル済みオブジェクト | 最大の効果 |
| ② Module Cache | /root/.cache/go-mod |
ダウンロード済みモジュール | モジュール再取得不要 |
| ③ Pkg Cache | /go/pkg/mod |
標準パッケージキャッシュ | 互換性向上 |
キャッシュの動作フロー

5. -mod=readonly フラグ
go.modの不変性を保証
go build -mod=readonly \
-tags=production \
-o main .
効果:
go.modの意図しない変更を防止- キャッシュの無効化を回避
- ビルドの再現性向上
6. 完全な静的バイナリ化
go build \
-ldflags="-s -w -extldflags '-static'" \
-o main .
フラグの意味
| フラグ | 効果 |
|---|---|
-s |
シンボルテーブル削除 |
-w |
DWARFデバッグ情報削除 |
-extldflags '-static' |
完全静的リンク |
-CGO_ENABLED=0の場合、Goはデフォルトで静的バイナリをビルドするため、-extldflags '-static'は明示する以外は不要です。
上記の対策を実施した結果::
- 実行時依存: 完全に排除
- バイナリサイズ: 約10-20%削減
- 28分 → 2分 (93%削減!)
まとめ
1. プロファイリングの重要性
問題を解決する前に、まず計測することが重要です。ボトルネックはどこにあるのか、まず改善コストが少なく結果が出やすい部分に着手しましょう。
# ビルド時間の詳細を確認
docker build --progress=plain -t myapp . 2>&1 | tee build.log
# パッケージ数を確認
go list -f '{{.ImportPath}}' ./... | wc -l
# 依存関係を確認
go mod graph | wc -l
2. キャッシュは複数層で
1層のキャッシュだけでなく、複数の層でキャッシュを活用:
- GitHub Actionsのキャッシュ
- Dockerのレイヤーキャッシュ
- BuildKitのキャッシュマウント
- Goのビルドキャッシュ
3. 不要なものを削除
今回の改善は環境へのデプロイする部分なので、それと関係ないものをすべて削除しました。
今後の改善案
8コアランナーの導入(さらなる高速化)
現在は標準の4コアランナーを使用していますが、有料プラン(GitHub Team/Enterprise)で利用可能な8コアランナーを使うことで、さらなる高速化が見込めます。
設定変更のポイント:
runs-on: ubuntu-latest-8-cores # 8コアランナーに変更 env: GO_BUILD_PARALLELISM: 16 # 並列度を倍増
期待される効果:
- 並列コンパイル数を8 → 16に増加
- ビルド時間: 2分17秒 → 1分30秒-2分(約20-30%削減)
- 総合削減率: 58分17秒 → 1分30秒-2分(97-98%削減)
コスト:
- コストは約2倍になるが、実行時間も短縮されるため実質コストはほぼ同等
- より高速なフィードバックループによる開発体験の向上
参考資料
Go Build関連
- Speeding up Go builds with build cache in Docker
- 20X Faster Golang Docker Builds
- Go Compilation Optimization: Master Techniques to Reduce Build Times by 70%
Docker & BuildKit
- Optimize cache usage in builds | Docker Docs
- Cache management with GitHub Actions
- Containerize Your Go Developer Environment - Part 2 | Docker
GitHub Actions
- GitHub ActionsでGoのコンテナイメージをビルド・プッシュする際のベストプラクティスを考える
- Speed Up Your Go Builds With Actions Cache
- reproducible-containers/buildkit-cache-dance
その他
最後まで読んでいただき、ありがとうございました!
この記事が、皆さんのGoプロジェクトのCI/CD改善の参考になれば幸いです。質問やフィードバックがあれば、ぜひコメントください!
明日の JMDC Advent Calendar 2025 もお楽しみに!
JMDCでは、ヘルスケア領域の課題解決に一緒に取り組んでいただける方を積極採用中です! フロントエンド /バックエンド/ データベースエンジニア等、様々なポジションで募集をしています。 詳細は下記の募集一覧からご確認ください。
まずはカジュアルにJMDCメンバーと話してみたい/経験が活かせそうなポジションの話を聞いてみたい等ございましたら、下記よりエントリーいただけますと幸いです。
★最新記事のお知らせはぜひ X(Twitter)、またはBlueskyをご覧ください!