GitHub Actions で書いた処理を、ひとつのリポジトリの中だけで使うのはもったいない。同じ処理を複数のリポジトリで使い回したい、あるいは他人のリポジトリに「これを入れてください」と配りたい。そう考えたとき、GitHub Actions には大きく2つの共通化の手段があります。Composite Action と Reusable Workflow です。
どちらでも「他リポジトリから処理を呼ぶ」は実現できます。ただ、両者は呼び出しの粒度も、secret の渡し方も、向いている場面も違います。私は LLM の生成処理を他リポジトリへ配布物として渡す設計を組んだとき、最初 Reusable Workflow で書いて、最終的に Composite Action へ移しました。その過程で見えた違いと、選ぶときの判断軸を整理します。
この記事で得られること
- Composite Action と Reusable Workflow の構造的な違い
- 呼び出し単位(ジョブ単位 / ステップ単位)がもたらす導入のしやすさの差
- secret の渡し方の違いと、それが設計に与える制約
- Composite Action 特有の
github.action_pathとgithub.workspaceの使い分け - 「配布物」として他リポジトリへ渡すときに、どちらを選ぶかの判断軸
まず、それぞれが何なのか
ざっくり言うと、共通化する「かたまりの大きさ」が違います。
-
Reusable Workflow は、ワークフロー(
.github/workflows/*.yml)そのものを共通化する仕組みです。on: workflow_callを付けたワークフローを別のワークフローから呼び出します。呼び出し側はjobs.<id>.usesで参照し、ジョブ1つ分をまるごと持ち込む形になります。 -
Composite Action は、複数のステップを1つの Action にまとめる仕組みです。
action.ymlにruns.using: compositeと書き、ステップ列を定義します。呼び出し側はstepsの中でusesで参照し、1ステップとして差し込みます。
この「ジョブ単位」か「ステップ単位」かの違いが、後のすべての差につながります。
違いを表で整理する
| 観点 | Reusable Workflow | Composite Action |
|---|---|---|
| 呼び出し単位 | ジョブ丸ごと(jobs.<id>.uses) |
ステップ単位(steps[].uses) |
| 既存ジョブへの組み込み | 別ジョブとして切り出す必要がある | 既存ジョブの1ステップとして差せる |
| secret の渡し方 |
secrets を明示的に渡せる(secrets: inherit も可) |
secrets コンテキストに直接アクセス不可。with(inputs)で渡す |
| マトリックス・並列制御 | ジョブレベルの strategy が使える |
呼び出し側ジョブの制御に従う |
| 呼び出し側の記述量 | やや多い(ジョブ定義が要る) | 少ない(1ステップ) |
| ネスト | 最大4レベル・累計20ワークフローまで | ステップ内で他 Action を呼べる |
表だけだと差が伝わりにくいので、場面ごとに分けて見ていきます。
違い1:呼び出し単位 — ジョブか、ステップか
いちばん大きな違いがここです。
Reusable Workflow はジョブ単位で呼びます。呼び出し側はこう書きます。
# Reusable Workflow を呼ぶ側
jobs:
call-generate:
uses: your-org/your-repo/.github/workflows/generate.yml@v1
with:
config: spec.config.json
secrets:
api-key: ${{ secrets.API_KEY }}
jobs.call-generate という独立したジョブとして立ち上がります。すでにビルドやテストのジョブがあるリポジトリでは、それらとは別の箱がもう1つ増える形です。参照は owner/repo/.github/workflows/file.yml@ref とワークフローファイルのフルパス + ref で書く必要があり、Action の owner/repo@ref という短縮形は使えません。
一方 Composite Action はステップ単位で呼びます。
# Composite Action を呼ぶ側
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
- name: Generate docs
uses: your-org/your-action@v1 # ← 既存ジョブの途中に1行差すだけ
with:
api-key: ${{ secrets.API_KEY }}
既存の build ジョブの、テストの直後にそのまま差し込めます。新しいジョブを切り出す必要はありません。
違い2:既存ジョブへの差し込みやすさ
呼び出し単位の差は、そのまま「導入のしやすさ」に直結します。
他人のリポジトリへ「これを入れてください」と配るとき、相手のリポジトリには大抵すでに CI が組まれています。ここで配布物が Composite Action なら、相手は既存ジョブに1行足すだけで済みます。ジョブの構成図を描き直す必要がありません。
Reusable Workflow だと、相手は呼び出し用のジョブを1つ足すことになります。チェックアウトや成果物の受け渡しを別ジョブ間でやり取りする場合は、actions/upload-artifact と download-artifact を挟むなど、ジョブをまたぐ取り回しも考えることになります。一手間多くなります。
配布物として渡すなら、相手の手数が少ないほうが導入のハードルは下がります。この点では Composite Action が有利でした。
違い3:secret の渡し方
ここは設計を左右する、見落としやすい違いです。
Reusable Workflow は secrets コンテキストを呼び出し側から明示的に受け取れます。secrets: inherit と書けば呼び出し元の secret をまとめて引き継ぐこともできます(ただし inherit が効くのは同一 organization / enterprise 内の呼び出しに限られ、外部リポジトリには継承されません)。secret を「秘密のもの」として型を保ったまま渡せる、という感覚です。
# Reusable Workflow 側の宣言
on:
workflow_call:
secrets:
api-key:
required: true
Composite Action は、ここが構造的に違います。Composite Action は secrets コンテキストに直接アクセスできません。だから secret を使いたいときは、呼び出し側が with(inputs)で明示的に値を渡す形になります。
# Composite Action 側の宣言(action.yml)
inputs:
api-key:
description: 利用する API キー
required: true
# 呼び出し側
- uses: your-org/your-action@v1
with:
api-key: ${{ secrets.API_KEY }} # ← secret を input へ流し込む
「secret なのに input として渡すのは気持ち悪い」と感じるかもしれません。これは好みではなく、Composite Action の仕様上そうするしかない、という必然です。
なお、GitHub は secrets.* 由来の値を自動でログマスクします。with 経由で渡しても、生のキーがログに出ることはありません。ただし base64 化するなどキーを加工した値はマスク対象から外れるので、加工して渡すときは注意が必要です。
secret に限らず、呼び出し元ジョブの env も Reusable Workflow / Composite Action へは自動伝播しません。内側で使いたい値は、secret であれ通常の値であれ with(や Reusable Workflow の secrets)で明示的に渡すのが基本です。
Composite Action 特有の落とし穴:action_path と workspace
Composite Action を配布物として書くとき、必ずぶつかるのが github.action_path と github.workspace の使い分けです。混同すると「自分のコードのつもりが相手のリポジトリを見ていた」という事故になります。
-
github.action_pathは、Action 自身のコードが展開される場所です。配布する処理のロジック本体(スクリプトや依存)はここから実行します。Action のコードは運営側のリポジトリに置いたまま、利用者のリポジトリにはコピーされません。 -
github.workspaceは、利用者のリポジトリがチェックアウトされた場所です。解析対象や、設定の読み込み元、生成物の出力先は、すべてこちら側を指します。
runs:
using: composite
steps:
- name: Run generator
shell: bash
working-directory: ${{ github.action_path }} # ← 自分のコードの場所
run: |
node "${{ github.action_path }}/dist/cli.js" \
--repo "${{ github.workspace }}" \ # ← 相手のリポジトリ
--out "${{ github.workspace }}/out"
この2つを混ぜないことで、「自分のロジックで・相手のコードを処理し・相手の場所へ出力する」という分離が成立します。配布物としての Composite Action では、ここが土台になります。
バージョニング:@main で配らない
これは両者に共通する注意ですが、配布物にするなら必ず守りたい点です。
利用者からの参照は @v1 のような moving major タグにします。@main 参照で配ると、こちら側の破壊的変更が予告なく全利用者に降ってきて、相手の CI が突然壊れます。メジャータグを切り、後方互換を約束できる範囲だけそのタグを進める。共通化された配布物の鉄則です。
- uses: your-org/your-action@v1 # ◎ moving major タグ
# - uses: your-org/your-action@main # ✗ 破壊的変更が即座に届く
private リポジトリで配布するときのアクセス設定
Composite Action も Reusable Workflow も、private リポジトリに置いたまま配布できます。ただし「どこから呼べるか」はアクセス設定しだいで変わります。public リポジトリは誰でも参照できますが、private は閉じているぶん、外部からの呼び出しを明示的に許可する必要があります。
配布先のパターンで整理すると、こうなります。
| 配布先 | 可否 | 必要な設定 |
|---|---|---|
| 同じリポジトリ内 | そのまま可能 | なし |
| 同じ organization / enterprise 内の別 private リポジトリ | 可能 | 配布元リポジトリでアクセス許可が必要 |
| 外部(別 organization・他者)の private リポジトリ | 不可 | public 化、または相手を collaborator / メンバーに追加 |
同じ organization 内で共有する場合は、Action やワークフローを置いた配布元のリポジトリ側で許可します。
Settings → Actions → General → Access
→ "Accessible from repositories in the '<org>' organization"
デフォルトは「このリポジトリ内のみ」です。ここを開け忘れると、呼び出し側の uses で参照が見つからないエラーになります。private リポジトリ間で参照がうまく解決できないときは、まずこの設定を疑うと早いです。
注意したいのは、外部の不特定多数へ配るケースです。相手が別 organization の private リポジトリだと、こちらの private リポジトリからは参照できません。広く配布したいなら、Action 自体を public リポジトリに置くのが基本になります。Action のコード(ロジック本体)を公開してよいか、が判断の分かれ目です。隠したいロジックがあるなら、コードを同梱する Composite Action ではなく、ビルド済みイメージを private registry に置く Docker Action など、別の配布形態が候補になります。
どちらを選ぶか
整理すると、判断軸はこうなります。
Composite Action が向いている場面
- 配布物として他リポジトリへ渡し、相手の既存ジョブに溶け込ませたい
- 「1ステップ追加するだけ」という導入の軽さを優先したい
- ビルドやテストの途中に、補助的な処理として挟みたい
Reusable Workflow が向いている場面
- ジョブレベルの
strategy(マトリックス)や並列制御をまとめて共通化したい - 複数ジョブからなる一連のパイプラインを丸ごと共通化したい
- secret を
secretsコンテキストとして型を保ったまま渡したい
私のケースでは、「他リポジトリの既存 CI に、生成処理を1ステップとして差し込んで配りたい」が要件の中心でした。だから Composite Action を選びました。先に「Action を使いたい」があったのではなく、要件を満たす入れ物を探した結果ここに着地した、という順番です。
この配布設計を「LLM の生成コストをユーザー側の CI に逃がす」という実例に当てはめた話は、別記事「LLM SaaSのコストが怖いので、生成をユーザーのCIに逃がした話」にまとめました。Composite Action を「3層境界を薄く配る帰結」として選んだ、より具体的な文脈はそちらにあります。
まとめ
Composite Action と Reusable Workflow は、どちらも処理の共通化に使えますが、出発点が違います。
- 呼び出し単位が違う。Reusable Workflow はジョブ単位、Composite Action はステップ単位。
- 差し込みやすさが違う。既存ジョブに溶け込ませるなら Composite Action、パイプラインごと共通化するなら Reusable Workflow。
-
secret の渡し方が違う。Composite Action は
secretsに直接触れずwithで渡す。Reusable Workflow はsecretsを型のまま渡せる。
「配布物として、相手の既存 CI に軽く差し込みたい」なら Composite Action、「ジョブ構成そのものを共通化したい」なら Reusable Workflow。この軸で選ぶと、迷いが少なくなります。