こんにちは!開発現場で日々コードと向き合っていると、「ローカルでは一瞬でビルドが終わるのに、なぜかGitHub Actions(CI)にプッシュした途端、ビルドが完了するまで何分も待たされる……」というもどかしい瞬間に直面したことはありませんか?
「毎回のビルドで、MavenやGradleが数年前の遺物のような大量の依存ライブラリをわざわざリモートからダウンロードし直している」
――これが、あなたのCI/CDパイプラインを遅くしている最大の犯人です。
今回は、世界中のJava/Kotlinエンジニアが直面するこの「CI遅延地獄」を鮮やかに解決し、「えっ、もうビルド終わったの?」と開発チーム全員が歓声を上げるような、最速のGitHub Actionsキャッシュ戦略と、無駄のないマルチステージビルドの実装術を、実務の現場目線で優しく、かつ徹底的に解説していきます。
これをマスターすれば、あなたの毎日のデプロイメント体験が劇的に軽くなりますよ。さあ、一緒に次世代のパイプラインを手に入れましょう!
—
1. なぜCIのビルドは遅くなるのか?(背景とツールの本質)
私たちが普段使っている Maven や Gradle は、非常に優秀な依存関係管理ツールです。プロジェクトが必要とするライブラリ(Spring BootやHibernateなど)を自動で解決し、ローカル環境(`~/.m2/repository` や `~/.gradle/caches`)にキャッシュしてくれます。
しかし、GitHub ActionsなどのクラウドCI環境は、原則として毎回まっさらな仮想マシン(ランナー)が使い捨て(エフェメラル)で起動します。
つまり、何も対策をしないと、CIが走るたびに以下のような非効率なドラマが繰り返されます。
1. クリーンな仮想マシンが立ち上がる
2. プロジェクトのコードがチェックアウトされる
3. 「おっ、ライブラリがないぞ」と気づき、世界中のリポジトリから数百MBに及ぶ依存JARファイルを毎回ダウンロードし始める
4. やっとコンパイルが始まり、テストが走る
これでは、ちょっとしたタイポの修正を直してプッシュするたびに、コーヒーを淹れに行く羽目になってしまいますよね。
これを解決するのが 「依存関係キャッシュ(Dependency Caching)」 です。前回のビルドで取得したライブラリの資産をどこかに保存しておき、次回のビルドではネットワークをバイパスして一瞬で復元する――これだけで、CIの実行時間を数分から数十秒へと劇的に短縮できます。
—
2. 【Maven編】超高速化を実現するGitHub Actions設定術
まずは、世界中のレガシーからモダンまで支える Maven の設定から見ていきましょう。
Mavenでキャッシュを効かせるための黄金律は、`~/.m2/repository` ディレクトリを GitHub Actions のキャッシュ機構に保存することです。
以下のワークフロー設定ファイルを `.github/workflows/ci-maven.yml` として配置してください。
name: Java CI with Maven
mainブランチへのプッシュ、またはプルリクエスト時に発火
on:
push:
branches: [ “main” ]
pull_request:
branches: [ “main” ]
jobs:
build:
# 最も安定している最新のUbuntu環境を指定
runs-on: ubuntu-latest
steps:
# 1. リポジトリのソースコードをランナーにチェックアウト
- name: Checkout repository
uses: actions/checkout@v4
# 2. 安定したEclipse Temurin(Open JDK)の環境をセットアップ
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: ’17’
distribution: ‘temurin’
# Maven自体のキャッシュも同時に有効化するとさらに吉
cache: ‘maven’
# 3. 最重要:Mavenのローカルリポジトリ(~/.m2/repository)をキャッシュする
- name: Cache Maven Dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
# pom.xmlが変更された時だけキャッシュが無効化(ハッシュ化)されるようにする
key: ${{ runner.os }}-maven-${{ hashFiles(‘/pom.xml’) }}
restore-keys: |
${{ runner.os }}-maven-
# 4. ビルドとテストの実行(依存関係のダウンロードがスキップされ、一瞬でコンパイルへ進む)
- name: Build with Maven
run: mvn clean verify
ここがエンジニアのこだわりポイント
`hashFiles(‘/pom.xml’)` をキャッシュのキーに指定している点がミソです。これにより、依存関係(`pom.xml`)に変更がない限り、前回の重いライブラリ群のキャッシュがそのまま再利用され、変更があった時だけ新しくキャッシュが再構築されるという、無駄のないスマートな仕組みが完成します。
—
3. 【Gradle編】インクリメンタルビルドとキャッシュの極意
次に、モダンなAndroid開発や大規模なJava/Kotlinバックエンドで主流となっている Gradle の設定です。
Gradleには、ファイル単位の変更検知を行う強力な「インクリメンタルビルド機能」と、ビルドタスク自体をキャッシュする「Gradle Build Cache」が存在します。
GitHub Actionsと組み合わせる際は、`~/.gradle/caches` と `~/.gradle/wrapper` をターゲットにしてキャッシュを構築します。
以下のファイルを `.github/workflows/ci-gradle.yml` として作成してください。
name: Java CI with Gradle
on:
push:
branches: [ “main” ]
pull_request:
branches: [ “main” ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: ’17’
distribution: ‘temurin’
# actions/setup-javaに組み込まれているGradleキャッシュ機能を利用
cache: ‘gradle’
# 権限付与(Gradle Wrapperを安全に実行するため)
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# Gradle専用のキャッシュ設定(より高度に制御したい場合)
- name: Cache Gradle Packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles(‘/.gradle’, ‘/gradle-wrapper.properties’) }}
restore-keys: |
${{ runner.os }}-gradle-
# テストとビルドの実行(–build-cache オプションでタスクキャッシュを有効化)
- name: Build with Gradle
run: ./gradlew build –build-cache
先輩からのアドバイス
Gradleを使うときは、`–build-cache` フラグを必ず付与してください。これにより、ソースコードに変更がないタスク(例えば「変化していないテストの再実行」など)が完全にスキップされ、CIのコストと時間が劇的に削減されます。
—
4. さらに先へ!「マルチステージビルド」で成果物を軽量化する
キャッシュによってビルド時間が速くなったら、次は「ビルド成果物のハンドリングとステージング」です。
実務の現場では、CIでビルドしたJARファイルをそのまま放置するのではなく、Dockerコンテナに詰め込んでKubernetesやECSなどの本番環境へデプロイすることがほとんどです。ここで重要になるのが、「ビルドする環境(重いJDKとビルドツールが必要)」と「実行する環境(軽量なJREだけで十分)」を完全に切り分けるマルチステージビルドの概念です。
Dockerを活用したマルチステージビルドの `Dockerfile` の実例を見てみましょう。
==========================================
ステージ1: ビルドステージ (重いJDKとGradle/Mavenを含む)
==========================================
FROM eclipse-temurin:17-jdk AS builder
WORKDIR /app
ソースコードをコンテナ内にコピー
COPY . .
Gradleを使ってアプリケーションをビルド(テストはCI側で終わらせている想定なら -x test でスキップも可)
RUN ./gradlew bootJar –no-daemon
==========================================
ステージ2: ランタイムステージ (本番用:軽量なJREのみ)
==========================================
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
ステージ1で生成された「実行に必要なJARファイルだけ」をマルチステージコピーする
これにより、Gradle本体やソースコードなどの不要な肥大化ファイルを一切含まない、セキュアで超軽量な本番イメージが完成する
COPY –from=builder /app/build/libs/.jar app.jar
コンテナ起動時に実行するコマンド
ENTRYPOINT [“java”, “-jar”, “app.jar”]
なぜこの構成が最強なのか?
ステージ1のビルド環境には数百MBあるJDKやビルドキャッシュが含まれますが、ステージ2(最終的な本番イメージ)には、動作に最低限必要なJREと軽量なJARファイル(数十MB程度)しか持ち込まれません。
これにより、イメージの脆弱性(CVE)を最小限に抑えつつ、コンテナのプッシュ・プル速度も劇的に向上します。
—
5. 動作確認:いざ、最速のパイプラインを体感せよ!
ここまで設定できたら、実際にGitHubへコードをプッシュしてみましょう。
1. リポジトリの `Actions` タブを開く。
2. ワークフローが走り始めるのを確認する。
3. 初回実行時は通常通りの時間がかかりますが、2回目以降のプッシュ(あるいはブランチへのマージ)を行ってみてください。
ログ画面を開き、「Post-job cleanup」や「Restore cache」のセクションを確認すると、一瞬でキャッシュが復元され、依存関係のダウンロード工程が驚くほど短縮されていることが目視できます。
「おっ、速い……!」
この瞬間を味わうと、もうキャッシュなしのCIには戻れなくなっているはずです。
—
まとめ
今回は、GitHub ActionsにおけるMaven/Gradleのキャッシュ戦略と、実務で必須となるマルチステージビルドの極意について解説しました。
- キャッシュ戦略の核心:`pom.xml` や `build.gradle` のハッシュ値をキーにして、ローカルリポジトリを丸ごとキャッシュする。
- ツールの使い分け:Mavenは `~/.m2/repository`、Gradleは `~/.gradle/caches` と `–build-cache` を活用する。
- コンテナ連携:マルチステージビルドを用いて、本番イメージには実行に必要な成果物だけを美しく封じ込める。
これをマスターすれば、あなたのチームの開発フィードバックループは圧倒的に加速し、コードを書く喜びが何倍にも膨れ上がります。
明日の開発から、ぜひあなたのプロジェクトに取り入れてみてくださいね。それでは、快適なCI/CDライフを!