コンテンツにスキップ

GitHub Actionsで差分があるサービスのみビルドする仕組み

概要

モノレポ構成で複数サービスがある場合、変更のないサービスまでビルドするのは時間・コストの無駄。 差分のあるサービスだけをビルドする仕組みをGitHub Actionsで実装した。

実装の考え方

ステップ1: Dockerfileがあるディレクトリを抽出

# リポジトリ内のすべてのDockerfileを探してサービス名リストを作る
find . -name "Dockerfile" | sed 's|/Dockerfile||' | sed 's|^./||'
# 出力例: services/api, services/worker, services/frontend

ステップ2: git diffで変更ファイルを取得

# mainブランチとの差分ファイル一覧を取得
git diff --name-only origin/main HEAD
# 出力例:
# services/api/main.go
# services/api/handler.go
# README.md

ステップ3: ビルド対象を判定

差分ファイルのパスにサービスディレクトリが含まれているかを照らし合わせる。

# services/api に変更があればビルド対象に追加
for service in $(find . -name "Dockerfile" -exec dirname {} \;); do
  if git diff --name-only origin/main HEAD | grep -q "^${service}/"; then
    echo "$service" >> build_targets.txt
  fi
done

GitHub ActionsのYAML例

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      services: ${{ steps.detect.outputs.services }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # git diffのために全履歴が必要
      - id: detect
        run: |
          SERVICES=$(find . -name "Dockerfile" -exec dirname {} \; | while read dir; do
            if git diff --name-only origin/main HEAD | grep -q "^${dir}/"; then
              echo $dir
            fi
          done | jq -R -s -c 'split("\n") | map(select(length > 0))')
          echo "services=$SERVICES" >> $GITHUB_OUTPUT

  build:
    needs: detect-changes
    if: needs.detect-changes.outputs.services != '[]'
    strategy:
      matrix:
        service: ${{ fromJson(needs.detect-changes.outputs.services) }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t ${{ matrix.service }} ${{ matrix.service }}

ポイント

  • fetch-depth: 0 を忘れると git diff が使えない(浅いクローンはコミット履歴がないため)
  • Dockerfileの場所とサービスのルートディレクトリが一致している構成が前提
  • matrix strategyを使うことで差分サービスを並列ビルドできる