1. はじめに

複数リポジトリで似たような CI/CD を書き続けていると、修正のたびに同じ YAML を何箇所もコピペしているはずです。GitHub Actions の Reusable Workflows(再利用可能ワークフロー) は、ワークフロー全体を「外から呼べる関数」のように扱える仕組みで、組織横断のテンプレート化と運用統一に最適です。本記事では、Composite Action との違い、呼び出し構文、inputs / secrets / outputs の設計、matrix との併用、ネストとセキュリティの注意点までを公式仕様に沿って整理します。

GitHub Actions 全般の使い所は Flutter アプリの CI/CD をどう組むか でも触れていますので、合わせてご覧ください。

2. Reusable Workflow とは

Reusable Workflow は、on: workflow_call: トリガを持つ通常のワークフロー YAML です。他のワークフロー(caller)から job の uses: キーで呼び出される側 として動作し、入力 (inputs)、シークレット (secrets)、出力 (outputs) を介してデータをやり取りします。

Reusable Workflow の caller / callee 関係図。caller の job が uses で reusable workflow を呼び出し、inputs と secrets を渡し、outputs を受け取る流れを示す

主な特徴を整理します。

  • 再利用の単位: ワークフロー全体(複数 job、matrix、permissions、runner 指定を含む)。
  • 呼び出し位置: caller 側の job レベルuses: を書く(step ではない)。
  • 隔離性: reusable 側は独立したワークフロー実行として走り、ログも別ビューで確認できる。
  • 配置場所: .github/workflows/ 直下のみ。サブディレクトリに置いたファイルは呼び出せません。

3. Composite Action との違い

「再利用」というと Composite Action と混同しがちですが、抽象化のレイヤが違うため、使い分けが必要です。

Reusable Workflow と Composite Action の比較概念図。左は job 単位で複数 job と matrix を含む Reusable Workflow、右は単一の step として展開される Composite Action

観点 Reusable Workflow Composite Action
再利用の単位 ワークフロー(複数 job) 1 つの step に展開される一連の手順
呼び出し場所 caller の job 直下 uses: caller の step 直下 uses:
自前の runs-on 持てる(job ごとに指定) 持てない(caller の runner を使う)
strategy.matrix 内部の job で使える 不可(caller 側 job が持つ matrix に従う)
permissions 自前で指定可(caller より同等以下のみ) 不可(caller の job 設定を継承)
secrets on.workflow_call.secrets で受け取り inputs として明示的に渡す必要あり
secrets: inherit 使える 使えない
配置パス .github/workflows/ 直下のみ リポジトリ内任意(通常 .github/actions/*/
典型用途 「ビルド〜デプロイ」など大きな job の塊 「依存セットアップ」「キャッシュ」等の手順束

使い分けの目安: matrix・複数 job・別 runner・OIDC を含む大粒度の処理は Reusable Workflow、共通の数ステップを束ねたいだけなら Composite Action、と分けるとシンプルです。

4. 作成方法 — 呼ばれる側 YAML

呼ばれる側は .github/workflows/ 直下に置き、on: workflow_call セクションで inputs / secrets / outputs を宣言します。以下は Azure 環境にデプロイする実用サンプルです。

# .github/workflows/reusable-deploy.yml
name: Reusable Deploy

on:
  workflow_call:
    inputs:
      environment:
        description: "デプロイ先環境名 (dev / stage / prod)"
        required: true
        type: string
      enable-smoke-test:
        description: "デプロイ後にスモークテストを実行するか"
        required: false
        type: boolean
        default: true
    secrets:
      AZURE_CREDENTIALS:
        description: "azure/login で使うサービスプリンシパル JSON"
        required: true
    outputs:
      deploy-url:
        description: "デプロイされたアプリの URL"
        value: ${{ jobs.deploy.outputs.deploy-url }}

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    outputs:
      deploy-url: ${{ steps.do-deploy.outputs.url }}
    steps:
      - uses: actions/checkout@v4

      - name: Azure login
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Deploy to ${{ inputs.environment }}
        id: do-deploy
        run: |
          echo "Deploying to ${{ inputs.environment }} ..."
          # 実際のデプロイコマンドに置き換える
          URL="https://app-${{ inputs.environment }}.example.com"
          echo "url=$URL" >> "$GITHUB_OUTPUT"

      - name: Smoke test
        if: ${{ inputs.enable-smoke-test }}
        run: curl -fsS "${{ steps.do-deploy.outputs.url }}/health"

ポイント:

  • inputstypeboolean / number / string を指定できます。
  • outputsstep output → job output → workflow_call output の三段で持ち上げます。
  • permissions は caller から渡された権限以下に絞り込みます(昇格は不可)。
  • on.workflow_call のトリガー定義自体には environment: キーを指定できません。ただし呼ばれる側の job レベルenvironment: を指定すること自体は可能で、その場合は caller から渡されたシークレットではなく、その environment 自身に設定されたシークレットが優先される点に注意してください。

5. 呼び出し方 — 呼ぶ側 YAML

caller 側は job の直下に uses: を書きます。step の uses: ではない点が最大のハマりどころです。

# .github/workflows/caller.yml
name: Deploy pipeline

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    strategy:
      fail-fast: false
      matrix:
        target: [dev, stage, prod]
    # SHA ピン留めで再現性とサプライチェーンの安全性を確保
    uses: my-org/platform-workflows/.github/workflows/reusable-deploy.yml@2f1c4a9d8b6e0f3c5a7d9e2b4c6f8a0d1e3b5c7d
    with:
      environment: ${{ matrix.target }}
      enable-smoke-test: true
    secrets: inherit

  notify:
    needs: deploy
    runs-on: ubuntu-latest
    steps:
      - name: Show deployed URLs
        run: |
          echo "Last deploy URL: ${{ needs.deploy.outputs.deploy-url }}"

呼び出し構文は 2 通りです。

呼び出し先 構文 ref の扱い
他リポジトリ {owner}/{repo}/.github/workflows/{file}.yml@{ref} SHA / tag / branch。タグとブランチ同名はタグ優先
同一リポジトリ ./.github/workflows/{file}.yml @ref 不可・式不可。caller の commit から読まれる

secrets: inherit は同じ org / enterprise 内の reusable に対し、caller が持つシークレットをそのまま渡せる便利な指定です。ただし伝播するのは 直接の呼び先 1 段のみで、さらに先の reusable に渡したい場合は明示的に secrets: で再パスする必要があります。

6. matrix との併用

strategy.matrix を caller 側の job に書くと、matrix の各組み合わせごとに reusable workflow が並列起動します。上記サンプルでは target: [dev, stage, prod] の 3 環境が独立してデプロイされます。

注意点として、matrix 経由で並列起動した場合の needs.<job>.outputs.<name> は「最後に成功した job の値」が入ります。環境ごとの URL を個別に保持したい場合は、各 matrix の出力をファイルや artifact、または GitHub の Deployment API に書き出す設計にしておくと安全です。

7. ネストとセキュリティの注意点

Reusable Workflow は最大 10 階層まで入れ子にできます(caller + reusable 9 段)。ただし以下の制約があります。

  • ループ不可: A → B → A のような循環は禁止。
  • 権限は維持または縮小のみ: 子側で permissions を昇格させることはできません。
  • secrets: inherit は 1 段のみ: 深いネストでは各段で明示的に渡し直す。
  • 同一リポジトリの reusable: ./.github/workflows/file.yml 形式は caller のコミットから読み込まれるため、ref を別に指定することはできません。

セキュリティ運用のベストプラクティスは以下の通りです。

  • SHA ピン留め: @<40 桁 SHA> を基本にし、サプライチェーン攻撃面を狭める。
  • タグ/リリースでのバージョニング: 内製の platform リポジトリ側で v1 / v1.2.3 を発行し、利用側は SHA で参照する運用が現実的。
  • 最小 permissions: caller 側で permissions: {} から始め、必要分だけ追加。
  • Enterprise Cloud では audit log API で reusable workflow の利用状況を可視化できる。
  • 組織テンプレート: .github リポジトリの workflow-templates/ に置いて、新規リポジトリ作成時に推奨できるようにする。

8. まとめ

Reusable Workflow は、Composite Action では届かない「job 単位・複数環境・OIDC を含む大粒度の自動化」を共通化する手段として強力です。本記事のサンプル(reusable-deploy.yml / caller.yml)をたたき台に、まずは社内のデプロイ系ワークフローを 1 本 reusable 化するところから始めるのがおすすめです。

GitHub Actions の他の活用事例は Flutter アプリの CI/CD をどう組むか も参考になります。

9. 参考リンク


この記事の執筆にあたり、AI の支援を受けています。