スキップしてメイン コンテンツに移動

【Day 45】GitHub ActionsキャッシュによるDockerビルド爆速化 〜Buildxとtype=ghaでCIビルド時間を80%削減!大容量.NET SDK&NuGetダウンロードをスキップする実践テクニック〜

Day 45 アイキャッチ
▲ Day 45: GitHub ActionsキャッシュによるDockerビルド爆速化 〜Buildxとtype=ghaでCIビルド時間を80%削減!大容量.NET SDK&NuGetダウンロードをスキップする実践テクニック〜
⚡

「プルリクエストを更新するたびに15分待たされ、開発リズムが完全に崩壊していませんか?」

前回のDay 44では、Gitタグ発行をトリガーにSemVerバージョニングされたDockerイメージをGHCRへ自動公開する、完全トレーサビリティのリリースパイプラインを構築しました。
しかし、CI/CDパイプラインを実務で運用し始めると、チーム全員が直面する巨大な壁があります。
「CIのDockerビルドが遅すぎる……!たった1行のコード修正なのに、なぜ毎回15分も待たされるのか?」
ローカル環境では数秒で終わるDockerビルドが、なぜGitHub Actions上では毎回全工程を最初からやり直してしまうのでしょうか?
本講義(Day 45)では、Docker Buildx と GitHub Actions公式のキャッシュ機能 type=gha を完全連携させ、大容量の.NET SDKや数十個のNuGetパッケージダウンロードをスキップしてビルド時間を80%以上削減(15分 ➔ 2分未満)する極意を徹底解説します!

📌 本日のアジェンダ(講義目次)

  • 第1章:なぜCIのDockerビルドは遅いのか? エフェメラル環境の罠
  • 第2章:Docker Buildx と type=gha キャッシュの仕組み
  • 第3章:キャッシュヒット率を100%引き出す C# Dockerfile 設計術
  • 第4章:GitHub Actions ワークフローの実装(cache-from / cache-to)
  • 第5章:C#/.NET プロジェクトにおける劇的な高速化検証と運用ルール

1 なぜCIのDockerビルドは遅いのか? エフェメラル環境の罠

手元の開発PC(Docker DesktopやWSL2)で docker build を実行すると、2回目以降のビルドは一瞬で終わります。Dockerが過去にビルドした中間レイヤーをローカルディスクに保存し、CACHED として再利用してくれるからです。

しかし、同じDockerfileをGitHub Actions上でビルドすると、毎回すべてのレイヤーが最初から実行されてしまいます。

毎回ゼロからビルド vs キャッシュ再利用の爆速化
▲ 【図解①】CI実行ごとの全レイヤー再ビルドによる15分の待ち時間を、GitHub Actionsキャッシュ活用で2分へ短縮

この原因は、GitHub Actionsのホステッドランナー(ubuntu-latest など)がエフェメラル(使い捨て)な仮想マシンとして起動する仕様にあります。

⚠️ エフェメラル環境が引き起こす3大ボトルネック
  • ディスクの完全破棄:ワークフローが終了するたびに仮想マシン全体が破棄されるため、ローカルのDockerレイヤーキャッシュが次回実行時に引き継がれない。
  • 巨大SDKの毎回ダウンロード:mcr.microsoft.com/dotnet/sdk:8.0(約800MB〜1GB以上)を毎回インターネット経由でプルする通信オーバーヘッド。
  • NuGetパッケージの全件復元:数十個の外部ライブラリを毎回ゼロからダウンロード・解凍するため、CPUと回線を浪費し10分以上待たされる。

この結果、PRを作成・コミットするたびに15分近いビルド待ちが発生し、エンジニアは「CIが終わるまで別の仕事に手がつかない」というコンテキストスイッチの地獄に陥ります。さらに、GitHub Actionsの月間実行時間枠(無料枠2,000分やチーム課金枠)が急激に枯渇していきます。

2 Docker Buildx と type=gha キャッシュの仕組み

この問題を根本から解決するのが、次世代ビルドエンジン BuildKit を活用する Docker Buildx と、GitHub Actions専用のキャッシュバックエンド type=gha です。

Docker Buildx と type=gha の仕組み
▲ 【図解②】cache-from でキャッシュ取得 ➔ 差分のみビルド ➔ cache-to (mode=max) で保存する3ステップフロー

従来の docker build はローカルディスク上のキャッシュしか扱えませんでした。しかし、Buildxは「外部ストレージに対してキャッシュをエクスポート/インポートする能力」を持っています。

💡 Buildx キャッシュパイプラインの3ステップフロー

  1. Step ① キャッシュ照会(cache-from: type=gha):
    ビルド開始時、GitHub Actions Cache APIと通信し、前回のビルドで保存されたレイヤーキャッシュを自動ダウンロードして展開します。
  2. Step ② 差分高速ビルド:
    変更のないレイヤー(ベースイメージ取得やNuGet復元など)を完全スキップ!変更されたソースコードのコンパイルのみを数秒で完了させます。
  3. Step ③ キャッシュ保存(cache-to: type=gha,mode=max):
    新しく更新・生成された中間レイヤーキャッシュを、GitHub Actions Cacheストレージへ自動的にアップロードして次回実行に備えます。

ここで極めて重要なのが、cache-to のオプションである mode=max です。デフォルトの mode=min では最終出力イメージのレイヤーしかキャッシュされませんが、mode=max を指定すると、マルチステージビルドの「ビルド中間ステージ(SDK環境やdotnet buildレイヤー)」も含めた全レイヤーがキャッシュ対象となります。

3 キャッシュヒット率を100%引き出す C# Dockerfile 設計術

Buildxとtype=ghaを導入しても、Dockerfileの命令順序が悪いとキャッシュは一切効きません。

Dockerのレイヤーキャッシュには「ある命令でキャッシュが無効化(キャッシュミス)すると、それ以降のすべての後続レイヤーも連鎖的に再実行される」という鉄則があるからです。

キャッシュヒット率を最大化するDockerfile順序
▲ 【図解③】csproj先行コピーによるdotnet restoreのキャッシュ保護 vs 全コピーによるキャッシュ崩壊の対比

よくあるアンチパターンと、キャッシュヒット率を最大化するベストプラクティスを比較してみましょう。

❌ やってはいけないアンチパターン(キャッシュ崩壊)

# アンチパターン:コード全体をはじめにCOPYしてしまう
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .  # 💥 1文字修正しただけで、これ以降の全キャッシュが無効化!
RUN dotnet restore "MyApi.csproj"
RUN dotnet build "MyApi.csproj" -c Release -o /app/build

ソースコードの1行を修正しただけで COPY . . のチェックサムが変わり、その直下の RUN dotnet restore が毎回全件再実行されてしまいます。

✅ 黄金律:依存関係先行コピー(キャッシュ最大化)

# ステージ1: ビルド環境
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

# ① 変更頻度が極めて低いプロジェクト定義ファイルのみを先行COPY
COPY ["MyApi/MyApi.csproj", "MyApi/"]
COPY ["MyLibrary/MyLibrary.csproj", "MyLibrary/"]

# ② NuGetパッケージ復元を実行(csprojが同一なら100%キャッシュヒット!)
RUN dotnet restore "MyApi/MyApi.csproj"

# ③ ここで初めて、日常的に変更されるC#ソースコードをCOPY
COPY . .
WORKDIR "/src/MyApi"
RUN dotnet build "MyApi.csproj" -c Release -o /app/build

# ステージ2: 発行
FROM build AS publish
RUN dotnet publish "MyApi.csproj" -c Release -o /app/publish /p:UseAppHost=false

# ステージ3: 実行用ランタイム環境(軽量)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]

このように、「変更頻度が低い命令を上に、頻繁に変更されるコードを下に配置する」ことで、日々のGitコミット(C#ロジック修正)では dotnet restore が完全にスキップされ、超高速な差分ビルドが実現します。

4 GitHub Actions ワークフローの実装(cache-from / cache-to)

それでは、実際の GitHub Actions ワークフローファイル(.github/workflows/docker-build.yml)に Buildx と type=gha キャッシュを組み込んでみましょう。

📄 .github/workflows/docker-build.yml (Buildx高速化完全構成)
name: Docker Build & Push with GitHub Actions Cache

on:
  push:
    branches: [ "main", "develop" ]
    tags: [ "v*" ]
  pull_request:
    branches: [ "main" ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: リポジトリのチェックアウト
        uses: actions/checkout@v4

      # 1. QEMUのセットアップ(マルチプラットフォーム対応)
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      # 2. Docker Buildx のセットアップ(次世代BuildKitエンジンを有効化)
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # 3. GHCRへのログイン(PR時は不要な場合スキップ可能)
      - name: Log in to GitHub Container Registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # 4. メタデータの抽出(タグとラベルの自動生成)
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=ref,event=branch
            type=ref,event=pr

      # 5. Buildx によるビルド&プッシュ(ghaキャッシュ有効化)
      - name: Build and push Docker image with Cache
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          # 🚀 ここが爆速化の核心!
          cache-from: type=gha
          cache-to: type=gha,mode=max
🔍 キャッシュ設定の重要ポイント
  • cache-from: type=gha:GitHub Actions のキャッシュストレージから、以前保存された同一ブランチ(または親ブランチ)のレイヤーキャッシュを自動取得します。
  • cache-to: type=gha,mode=max:中間ステージ(ビルド用SDKステージ)を含むすべてのレイヤーをキャッシュとして書き戻します。
  • 認証設定が不要:GitHub Actions内部のトークンを自動利用するため、追加のシークレット設定やAPIキーは一切不要です!

5 C#/.NET プロジェクトにおける劇的な高速化検証と運用ルール

それでは、実際に ASP.NET Core Web API プロジェクトで Buildx + type=gha キャッシュを導入した際のビフォーアフターの実測値を比較してみましょう。

C# /.NET プロジェクトにおけるCIビルド劇的短縮
▲ 【図解④】重い.NET SDK取得とNuGetパッケージ復元をスキップしたことによる圧倒的なビルド高速化とCI枠節約効果
ビルド項目 キャッシュなし(初回) Buildxキャッシュ(2回目以降) 短縮率
.NET SDK イメージ取得 1分45秒 0秒(完全キャッシュ) 100% OFF
dotnet restore(NuGet復元) 8分20秒 0秒(完全キャッシュ) 100% OFF
C# コンパイル & 発行 2分10秒 1分25秒(差分コンパイル) 35% 短縮
合計ビルド時間 12分15秒 1分48秒 🚀 約 85% 削減!

12分以上かかっていたビルド時間が1分台へ劇的に短縮されました!これにより、開発者はプルリクエスト作成後すぐにCIテストの結果を確認でき、開発リズムを妨げられることなく次のタスクへ進むことができます。

⚠️ 実務で知っておくべき運用の落とし穴と制限事項

  • リポジトリあたりのキャッシュ上限(10GB):
    GitHub Actions Cache はリポジトリ全体で最大 10GB までの制限があります。上限を超えると古いキャッシュから自動削除(LRU方式)されます。不要な一時ファイルがレイヤーに含まれないよう .dockerignore を徹底しましょう。
  • キャッシュ保持期間(7日間):
    アクセスされなかったキャッシュは7日後に自動破棄されます。長期間開発が止まっていたブランチでは初回ビルド時に再キャッシュが走ります。
  • ブランチ間のキャッシュスコープ:
    フィーチャーブランチは、デフォルトブランチ(main)のキャッシュを読み込むことができますが、フィーチャーブランチで作成したキャッシュは他のフィーチャーブランチからは参照できません。そのため、main ブランチで最新のキャッシュを常に温めておく(ウォームアップしておく)運用がベストです。

🎯 Day 45 の重要ポイント総括

  • CI環境はエフェメラル:通常のDockerビルドでは毎回仮想マシン破棄に伴いレイヤーキャッシュが消滅する。
  • Buildx + type=gha の導入:GitHub Actions専用の外部キャッシュストレージを活用することで、CIランナー間でレイヤーキャッシュを完全共有。
  • mode=max の指定:中間ビルドステージ(.NET SDKやビルド用レイヤー)まで含めて確実にキャッシュ保存する。
  • Dockerfile順序の徹底:.csproj のみ先行COPY ➔ dotnet restore ➔ ソースコードCOPY の順序を守り、キャッシュヒット率を最大化する。
  • 80%以上のビルド短縮:大容量.NET SDK取得とNuGet全復元をスキップし、開発リズムとCIコストを大幅改善!

📝 理解度チェックテスト

本日の講義内容を復習してみましょう。クリックすると正解と解説が表示されます。

Q1: GitHub ActionsでDocker Buildxキャッシュを設定する際、中間ビルドステージも含めてすべてキャッシュさせるためのオプションはどれ?
正解:cache-to: type=gha,mode=max
デフォルトの mode=min では最終成果物イメージのレイヤーしかキャッシュされません。マルチステージビルドのSDK環境や中間コンパイル成果物をキャッシュするには mode=max の指定が必須です。
Q2: C# の Dockerfile で COPY . . を RUN dotnet restore より前に書いてはいけない最大の理由は何?
正解:C#コードを1行変更しただけで、後続の dotnet restore のキャッシュが無効化されてしまうため
.csproj に変更がなくても、直前の COPY . . で変更が検知されるとDockerはキャッシュを破棄し、時間のかかるNuGetパッケージ全件復元を再度やり直してしまいます。
Q3: GitHub Actions Cache のリポジトリごとの容量制限と非アクセス時の保持期間はそれぞれどれくらい?
正解:容量上限 10GB / 保持期間 7日間
10GBを超過した場合は古いキャッシュから自動削除されます。また、7日間一度もアクセスされなかったキャッシュも自動的にクリーンアップされます。

NEXT LECTURE

Day 46: プルリクエスト(PR)ごとの一時的プレビュー環境の自動構築

次回は、PRを作成するたびに独立したテスト用コンテナ環境がクラウドに立ち上がり、マージ・クローズ時に自動破棄される「エフェメラル環境(Ephemeral Environments)」の構築に挑戦します!
ステージングサーバーの順番待ちに終止符を打つ、現代的開発フローの極意をお楽しみに!

コメント

このブログの人気の投稿

【Perfume】Perfume 3rd Tour 「JPN」さいたまスーパーアリーナに行って参りました~

Perfume 3rd Tour 「JPN」1月28日、29日の2daysで参戦して参りました。 ファンクラブ会員の選考抽選会に28日、29日の両日で申し込んでいたんですが、両方とも当選していたという嬉しい誤算。 仕事だぁ、なんだかんだと紆余曲折ありまして28日の参戦は危ぶまれていたのですが、なんとかかんとか28日は40分遅刻しながらも参戦、29日はフル参戦する事が出来ました(∩´∀`)∩

Perfume、2025年末で“コールドスリープ(活動休止)”へ──東京ドーム2daysの記録と、公式・各界・ファンの声

  1) 公式発表・基本情報 活動休止(コールドスリープ)を正式発表 2025年9月21日、公式サイト「 Perfumeより 」で、2026年から一度コールドスリープに入ると発表。再始動の意思(“新しいPerfume”として戻る)も明記。 Perfume Official Site 今後の運用告知(配信・上映など) 同21日付の「 今後のPerfumeに関して 」で、9/23(火・祝)東京ドーム公演の LIVESHIP全世界生配信 および 全国ディレイビューイング 決定を案内。特設サイトも併記。 Perfume Official Site +1 公式グッズ **「Perfume Calendar 2026」**発売決定(B2壁掛け/表紙+2カ月1枚×6枚+ポスター)。テーマは“フルーツ”。 Perfume Official Site 2) 東京ドーム2days(9/22・23)要点 公演タイトル: ZO/Z5 Anniversary “ネビュラロマンス” Episode TOKYO DOME 。日程・開場/開演・会場・物販等の実務情報は特設で公開。 Perfume Official Site 9/23公演を世界配信(LIVESHIP) /ディレイビューイング実施。 Perfume Official Site 海外向け報道(英語)も「 indefinite hiatus 」として広く配信。 The Times of India 国内英字メディアも速報。 朝日新聞 3) 現地レポ( メリ爺 )から見える空気 あなたの現地記録(9/24公開)を、ファクトと現場情景の両面で反映: 両日参戦 。22日は アリーナ 、23日は 1階席から俯瞰 。前日の発表を受け、開演前のザワつき→MC後に“いつもの一体感”へ。開演は22日 18:45 頃。オープニングは**P³ツアー東京ドーム(2020/2)**と同趣向で“回収”の演出。 メリ爺のゲーム万歳 MC所感 :解散ではなく前向きな休止で不安を和らげる内容。 かしゆか の涙、 のっち の気丈さ、 あ〜ちゃん の思い出語り──3人の結束と「 また戻る 」約束が強く刻まれた。 メリ爺のゲーム万歳 体感時間 : “3時間弱”級のボリューム ながら“過去一...

ニンテンドースイッチでオンラインゲームを遊ぶには?

モンスターハンターライズが発売になって、家族でモンハン楽しみたい人が増えたみたいですね。 某ブログの検索キーワードを調べると、モンハンを家族でやるにはどうしたらいいのか?を調べている人が沢山いるのがわかります。 ※これでも一部  家族で協力するにはSwitch2台、ソフトも2つ購入する必要があります。 1台のスイッチでは遊べませんし、ソフト1つでも遊べません。 でも、1台のスイッチで別々アカウントがあれば交代で遊ぶこともできますよ。セーブデータもしっかり分かれます。 Switchのオンラインゲームを家族で遊ぶ方法については以下の記事にまとめてますのでよろしければ参考にしてみてくださいね。 家族でモンスターハンターライズDL版のオンラインプレーを楽しむ方法