GitHub Actions のジョブ分割設計:needs と outputs でデータを安全に受け渡す
GitHub Actions でワークフローを構築する際、複雑な処理を単一のジョブで実行するのではなく、複数のジョブに分割したいケースは多いですよね。たとえば、ビルド結果を複数の環境でテストしたい場合や、デプロイ前に複数の検証ステップを順番に実行したい場合などです。
しかし、ジョブを分割すると「前のジョブで生成したデータを次のジョブで使いたい」という課題が出てきます。この記事では、GitHub Actions の needs と outputs を使って、ジョブ間で安全にデータを受け渡す方法を詳しく解説します。実践的なコード例とともに、ジョブ分割設計のベストプラクティスをご紹介しますね。
背景
CI/CD パイプラインにおけるジョブ分割の重要性
現代のソフトウェア開発では、CI/CD パイプラインが複雑化しています。単一のジョブで全ての処理を行うと、以下のような問題が発生するでしょう。
まず、処理時間が長くなり、フィードバックが遅れてしまいます。次に、エラーが発生した際にどの処理で失敗したのか特定しづらくなりますね。さらに、並列化できる処理があっても順次実行されるため、全体の実行時間が無駄に長くなってしまうのです。
以下の図は、CI/CD パイプラインにおけるジョブ分割の基本的な考え方を示しています。
mermaidflowchart TD
start["コードプッシュ"] --> build["ビルド<br/>ジョブ"]
build --> test1["単体テスト<br/>ジョブ"]
build --> test2["統合テスト<br/>ジョブ"]
build --> lint["Lint チェック<br/>ジョブ"]
test1 --> deploy["デプロイ<br/>ジョブ"]
test2 --> deploy
lint --> deploy
deploy --> done["完了"]
この図から分かるように、ビルド後のテストや検証は並列実行できますが、デプロイは全てのテストが成功してから実行する必要があります。
ジョブ分割がもたらすメリット
ジョブを適切に分割すると、以下のようなメリットが得られます。
| # | メリット | 説明 |
|---|---|---|
| 1 | 並列実行による高速化 | 依存関係のないジョブを同時実行し、全体の処理時間を短縮できます |
| 2 | 再実行の効率化 | 失敗したジョブだけを再実行でき、成功したジョブの結果を再利用できます |
| 3 | 責任の明確化 | 各ジョブの役割が明確になり、メンテナンスしやすくなります |
| 4 | デバッグの容易さ | どのステップで問題が発生したのか一目で分かるようになります |
| 5 | リソースの最適化 | ジョブごとに異なる実行環境を選択でき、コストを最適化できます |
これらのメリットを最大限に活かすには、ジョブ間でデータを安全かつ効率的に受け渡す仕組みが不可欠なのです。
課題
ジョブ間でのデータ共有の難しさ
GitHub Actions では、各ジョブは独立した仮想環境(ランナー)で実行されます。つまり、ジョブ A で生成したファイルやデータは、デフォルトではジョブ B からアクセスできません。
以下の図は、ジョブが独立した環境で実行される様子を示しています。
mermaidflowchart LR
subgraph runner1["ランナー 1"]
jobA["ジョブ A<br/>バージョン番号<br/>を生成"]
end
subgraph runner2["ランナー 2"]
jobB["ジョブ B<br/>バージョン番号<br/>を使いたい"]
end
jobA -.->|"データが<br/>渡らない"| jobB
この図から、ジョブ間で直接データを共有できない課題が見て取れますね。
よくある間違ったアプローチ
ジョブ間でデータを共有しようとして、以下のような間違ったアプローチを取ってしまうケースがあります。
環境変数の誤用
ジョブ内で設定した環境変数は、そのジョブ内でしか有効ではありません。以下のコードは動作しません。
yamljobs:
job-a:
runs-on: ubuntu-latest
steps:
# このenv設定は他のジョブには影響しない
- name: 環境変数を設定
run: echo "VERSION=1.2.3" >> $GITHUB_ENV
ファイルシステムへの依存
各ジョブは異なるランナーで実行されるため、ファイルに書き込んでも他のジョブからは読み取れません。
yamljobs:
job-a:
runs-on: ubuntu-latest
steps:
# このファイルは他のジョブからアクセスできない
- name: ファイルに書き込み
run: echo "1.2.3" > version.txt
データ受け渡しに必要な要件
ジョブ間でデータを受け渡すには、以下の要件を満たす必要があります。
| # | 要件 | 重要度 | | --- | ------------------ | ---------------------------------------------- | --- | | 1 | データの永続化 | ジョブが終了してもデータが保持される仕組み | ★★★ | | 2 | 安全なアクセス制御 | 意図したジョブだけがデータにアクセスできること | ★★★ | | 3 | 型安全性 | データの形式が保証されていること | ★★☆ | | 4 | シンプルな API | 簡単に使える記法であること | ★★★ | | 5 | パフォーマンス | データの受け渡しがボトルネックにならないこと | ★★☆ |
これらの要件を満たす解決策が、GitHub Actions の needs と outputs なのです。
解決策
needs キーワードによる依存関係の定義
needs キーワードを使うと、ジョブ間の依存関係を明示的に定義できます。これにより、特定のジョブが完了してから次のジョブを実行する順序制御が可能になりますね。
基本的な needs の構文は以下のとおりです。
yamljobs:
first-job:
runs-on: ubuntu-latest
steps:
- name: 最初のジョブ
run: echo "First job running"
続けて、依存関係を定義するコードです。
yamlsecond-job:
# first-jobが成功してから実行される
needs: first-job
runs-on: ubuntu-latest
steps:
- name: 2番目のジョブ
run: echo "Second job running"
複数のジョブに依存する場合は、配列形式で指定できます。
yamlfinal-job:
# test-job と lint-job の両方が成功してから実行
needs: [test-job, lint-job]
runs-on: ubuntu-latest
steps:
- name: 最終ジョブ
run: echo "All tests passed"
outputs による安全なデータの受け渡し
outputs を使うと、ジョブから他のジョブへ文字列データを安全に渡せます。outputs は GitHub Actions のメタデータとして保存され、needs で依存関係を定義したジョブからアクセスできるのです。
outputs の基本構文は以下のようになります。
yamljobs:
produce-data:
runs-on: ubuntu-latest
# ジョブレベルでoutputsを定義
outputs:
version: ${{ steps.get-version.outputs.version }}
build-time: ${{ steps.get-time.outputs.time }}
ステップでデータを生成し、outputs に設定するコードです。
yamlsteps:
- name: バージョン番号を取得
id: get-version
run: |
VERSION=$(date +%Y.%m.%d)
echo "version=$VERSION" >> $GITHUB_OUTPUT
続けて、時刻情報を取得するステップです。
yaml- name: ビルド時刻を取得
id: get-time
run: |
TIME=$(date +%H:%M:%S)
echo "time=$TIME" >> $GITHUB_OUTPUT
別のジョブから outputs にアクセスするコードは以下のとおりです。
yamlconsume-data:
needs: produce-data
runs-on: ubuntu-latest
steps:
- name: データを使用
run: |
echo "Version: ${{ needs.produce-data.outputs.version }}"
echo "Build Time: ${{ needs.produce-data.outputs.build-time }}"
outputs の仕組みと制約
outputs は GitHub Actions の内部メタデータとして保存されます。以下の図は、outputs がどのように保存され、アクセスされるかを示しています。
mermaidsequenceDiagram
participant JobA as ジョブ A
participant Meta as GitHub<br/>メタデータ
participant JobB as ジョブ B
JobA->>JobA: ステップでデータ生成
JobA->>Meta: GITHUB_OUTPUT に<br/>書き込み
Meta->>Meta: outputs として保存
JobB->>Meta: needs 経由で<br/>アクセス
Meta->>JobB: データを取得
outputs には以下のような制約があることを理解しておきましょう。
| # | 制約事項 | 詳細 |
|---|---|---|
| 1 | データ型 | 文字列のみ(JSON 文字列として複雑なデータも扱える) |
| 2 | サイズ制限 | 1 つの output は 1 MB まで |
| 3 | アクセス範囲 | needs で依存関係を定義したジョブからのみアクセス可能 |
| 4 | 数の制限 | 1 つのジョブにつき 256 個まで |
| 5 | 命名規則 | 英数字、ハイフン、アンダースコアのみ使用可能 |
これらの制約を理解した上で使うことが、安全なデータ受け渡しの鍵となります。
GITHUB_OUTPUT の使い方
GitHub Actions では、$GITHUB_OUTPUT 環境変数を使ってステップの outputs を設定します。以前は set-output コマンドが使われていましたが、セキュリティ上の理由から非推奨になりました。
現在推奨される方法は以下のとおりです。
bash#!/bin/bash
# シェルスクリプトでoutputsを設定
# 単純な値の設定
echo "result=success" >> $GITHUB_OUTPUT
# 変数を使った設定
VERSION="1.2.3"
echo "version=$VERSION" >> $GITHUB_OUTPUT
複数行の値を設定する場合は、デリミタを使います。
bash#!/bin/bash
# 複数行のデータをoutputsに設定
echo "logs<<EOF" >> $GITHUB_OUTPUT
echo "Line 1 of log" >> $GITHUB_OUTPUT
echo "Line 2 of log" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
JavaScript や TypeScript のアクションからも設定できます。
typescript// TypeScriptでoutputsを設定
import * as core from '@actions/core';
// 値を設定
core.setOutput('status', 'completed');
core.setOutput('count', '42');
具体例
例 1:ビルド情報を複数のジョブで共有
実際のプロジェクトでよくあるユースケースとして、ビルド情報を生成して複数のテストジョブで使用する例を見てみましょう。
まず、ビルド情報を生成するジョブを定義します。
yamlname: ビルドとテスト
on:
push:
branches: [main, develop]
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.value }}
artifact-name: ${{ steps.artifact.outputs.name }}
sha-short: ${{ steps.sha.outputs.short }}
続けて、各情報を生成するステップを定義します。
yamlsteps:
- name: コードをチェックアウト
uses: actions/checkout@v4
- name: バージョン番号を生成
id: version
run: |
VERSION=$(date +%Y.%m.%d)-${{ github.run_number }}
echo "value=$VERSION" >> $GITHUB_OUTPUT
アーティファクト名と短縮 SHA を生成するステップです。
yaml- name: アーティファクト名を生成
id: artifact
run: |
NAME="build-${{ steps.version.outputs.value }}"
echo "name=$NAME" >> $GITHUB_OUTPUT
- name: 短縮 SHA を取得
id: sha
run: |
SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
echo "short=$SHORT_SHA" >> $GITHUB_OUTPUT
ビルド情報を使って単体テストを実行するジョブは以下のようになります。
yamlunit-test:
needs: build
runs-on: ubuntu-latest
steps:
- name: ビルド情報を表示
run: |
echo "Version: ${{ needs.build.outputs.version }}"
echo "Artifact: ${{ needs.build.outputs.artifact-name }}"
echo "SHA: ${{ needs.build.outputs.sha-short }}"
統合テストジョブも同様にビルド情報を参照できます。
yamlintegration-test:
needs: build
runs-on: ubuntu-latest
steps:
- name: テスト環境を構築
run: |
echo "Testing version: ${{ needs.build.outputs.version }}"
# ビルド情報を使ってテスト環境をセットアップ
例 2:条件付きデプロイメント
テスト結果に基づいて、デプロイを実行するかどうかを決定する例です。以下の図は、このワークフローの全体像を示しています。
mermaidflowchart TD
test["テストジョブ"] --> check{"全テスト<br/>成功?"}
check -->|"success"| deploy["本番デプロイ"]
check -->|"failure"| skip["デプロイ<br/>スキップ"]
deploy --> notify["通知"]
skip --> notify
テスト実行と結果の出力を行うジョブです。
yamljobs:
test:
runs-on: ubuntu-latest
outputs:
test-result: ${{ steps.test.outputs.result }}
test-coverage: ${{ steps.test.outputs.coverage }}
steps:
- name: テストを実行
id: test
run: |
# テスト実行(例)
yarn test --coverage
# 結果を判定
if [ $? -eq 0 ]; then
echo "result=success" >> $GITHUB_OUTPUT
else
echo "result=failure" >> $GITHUB_OUTPUT
fi
カバレッジ情報を取得するステップです。
yaml- name: カバレッジを取得
run: |
COVERAGE=$(cat coverage/coverage-summary.json | \
jq -r '.total.lines.pct')
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
テスト結果に基づいてデプロイを実行するジョブは以下のとおりです。
yamldeploy:
needs: test
# テストが成功した場合のみ実行
if: needs.test.outputs.test-result == 'success'
runs-on: ubuntu-latest
steps:
- name: デプロイ実行
run: |
echo "Deploying with coverage: ${{ needs.test.outputs.test-coverage }}%"
# デプロイ処理
例 3:マトリックスビルドの結果集約
複数の環境でテストを実行し、全ての結果を集約する実践的な例を見てみましょう。
マトリックス戦略でテストを実行するジョブです。
yamljobs:
test-matrix:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
outputs:
# マトリックスジョブはoutputsを直接使えない
test-status: ${{ steps.test.outputs.status }}
各環境でテストを実行するステップです。
yamlsteps:
- name: Node.js セットアップ
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: テスト実行
id: test
run: |
yarn install
yarn test
echo "status=passed" >> $GITHUB_OUTPUT
マトリックスジョブの結果を集約する専用ジョブを定義します。
yamlaggregate-results:
needs: test-matrix
runs-on: ubuntu-latest
outputs:
all-passed: ${{ steps.check.outputs.result }}
steps:
- name: 全結果をチェック
id: check
run: |
# 全てのマトリックスジョブが成功したか確認
echo "result=true" >> $GITHUB_OUTPUT
例 4:JSON データの受け渡し
複雑なデータ構造を outputs で受け渡す場合は、JSON 文字列を使用します。
JSON データを生成して outputs に設定するジョブです。
yamljobs:
generate-config:
runs-on: ubuntu-latest
outputs:
config: ${{ steps.create-config.outputs.json }}
steps:
- name: 設定JSONを生成
id: create-config
run: |
CONFIG=$(cat <<EOF
{
"environment": "production",
"region": "us-east-1",
"replicas": 3,
"features": ["feature-a", "feature-b"]
}
EOF
)
JSON を outputs に設定する処理です。
yaml# 改行を削除してJSON文字列化
CONFIG_JSON=$(echo $CONFIG | jq -c .)
echo "json=$CONFIG_JSON" >> $GITHUB_OUTPUT
JSON データを受け取って使用するジョブは以下のとおりです。
yamluse-config:
needs: generate-config
runs-on: ubuntu-latest
steps:
- name: 設定を解析して使用
run: |
CONFIG='${{ needs.generate-config.outputs.config }}'
# jqで値を取り出す
ENV=$(echo $CONFIG | jq -r '.environment')
REGION=$(echo $CONFIG | jq -r '.region')
REPLICAS=$(echo $CONFIG | jq -r '.replicas')
echo "Environment: $ENV"
echo "Region: $REGION"
echo "Replicas: $REPLICAS"
例 5:動的なジョブ生成
outputs を使って、実行時に動的にジョブの挙動を変更する高度な例です。
変更されたファイルを検出して、必要なジョブを判定します。
yamljobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
frontend-changed: ${{ steps.changes.outputs.frontend }}
backend-changed: ${{ steps.changes.outputs.backend }}
docs-changed: ${{ steps.changes.outputs.docs }}
ファイル変更を検出するステップです。
yamlsteps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: 変更ファイルを検出
id: changes
run: |
# フロントエンドの変更チェック
git diff --name-only HEAD^ HEAD | grep -q "^frontend/" && \
echo "frontend=true" >> $GITHUB_OUTPUT || \
echo "frontend=false" >> $GITHUB_OUTPUT
バックエンドとドキュメントの変更をチェックします。
yaml # バックエンドの変更チェック
git diff --name-only HEAD^ HEAD | grep -q "^backend/" && \
echo "backend=true" >> $GITHUB_OUTPUT || \
echo "backend=false" >> $GITHUB_OUTPUT
# ドキュメントの変更チェック
git diff --name-only HEAD^ HEAD | grep -q "^docs/" && \
echo "docs=true" >> $GITHUB_OUTPUT || \
echo "docs=false" >> $GITHUB_OUTPUT
フロントエンドに変更があった場合のみテストを実行するジョブです。
yamltest-frontend:
needs: detect-changes
if: needs.detect-changes.outputs.frontend-changed == 'true'
runs-on: ubuntu-latest
steps:
- name: フロントエンドテスト
run: |
echo "Running frontend tests..."
cd frontend && yarn test
バックエンドに変更があった場合のみテストを実行します。
yamltest-backend:
needs: detect-changes
if: needs.detect-changes.outputs.backend-changed == 'true'
runs-on: ubuntu-latest
steps:
- name: バックエンドテスト
run: |
echo "Running backend tests..."
cd backend && yarn test
ベストプラクティス
ジョブ分割と outputs を使う際のベストプラクティスをまとめます。
| # | プラクティス | 理由 |
|---|---|---|
| 1 | outputs には小さなデータのみ | サイズ制限(1MB)があり、大きなデータはアーティファクトを使用 |
| 2 | 明確な命名規則 | build-version のように用途が分かる名前を使用 |
| 3 | JSON で複雑なデータを扱う | 構造化データは JSON 文字列として受け渡す |
| 4 | エラーハンドリングを実装 | outputs 生成時のエラーを適切に処理 |
| 5 | ドキュメント化 | 各 output の用途と形式をコメントで記載 |
以下は、これらのベストプラクティスを適用した例です。
yamljobs:
build:
runs-on: ubuntu-latest
outputs:
# ビルドバージョン(形式: YYYY.MM.DD-RUN_NUMBER)
build-version: ${{ steps.version.outputs.value }}
# デプロイ可能かどうか(true/false)
deployable: ${{ steps.validate.outputs.ready }}
# ビルド統計情報(JSON文字列)
build-stats: ${{ steps.stats.outputs.json }}
まとめ
GitHub Actions の needs と outputs を使ったジョブ分割設計について、詳しく解説してきました。重要なポイントを振り返ってみましょう。
まず、ジョブを適切に分割することで、並列実行による高速化、再実行の効率化、責任の明確化といったメリットが得られます。しかし、各ジョブは独立した環境で実行されるため、データの受け渡しには工夫が必要でした。
needs キーワードを使うことで、ジョブ間の依存関係を明示的に定義できます。これにより、特定のジョブが完了してから次のジョブを実行する順序制御が可能になりますね。
outputs を使うと、ジョブから他のジョブへ文字列データを安全に渡せます。$GITHUB_OUTPUT に書き込むことで、GitHub Actions のメタデータとしてデータが保存され、needs で依存関係を定義したジョブからアクセスできるのです。
実践例では、ビルド情報の共有、条件付きデプロイ、マトリックスビルドの結果集約、JSON データの受け渡し、動的なジョブ生成など、様々なユースケースを見てきました。これらのパターンを組み合わせることで、複雑な CI/CD パイプラインも整理された形で実装できるでしょう。
outputs には 1MB のサイズ制限があることや、文字列のみを扱えることなど、いくつかの制約があります。しかし、これらの制約を理解した上で適切に使えば、安全で保守性の高いワークフローを構築できますね。
ぜひ、皆さんのプロジェクトでも needs と outputs を活用して、効率的な CI/CD パイプラインを構築してみてください。ジョブを適切に分割し、データを安全に受け渡すことで、開発者体験が大きく向上することでしょう。
関連リンク
articleGitHub Actions のジョブ分割設計:needs と outputs でデータを安全に受け渡す
articleGitHub Actions コンテキスト&式の早見表:fromJson/toJson/hashFiles まで
articleGitHub Actions で PostgreSQL/Redis を services で立ち上げるテスト基盤レシピ
articleGitHub Actions 署名戦略を比べる:SHA ピン留め vs タグ参照 vs バージョン範囲
articleGitHub Actions のキャッシュがヒットしない原因 10 と対処レシピ
articleGitHub Actions コンテキスト辞典:github/env/runner/secrets の使い分け最速理解
articleMistral 使い方入門:要約・説明・翻訳・書き換えの基礎プロンプト 20 連発
articleGitHub Actions のジョブ分割設計:needs と outputs でデータを安全に受け渡す
articleGit rev-spec チートシート:^/~/A..B/A...B を完全図解
article【早見表】JavaScript MutationObserver & ResizeObserver 徹底活用:DOM 変化を正しく監視する
articlehtmx × Laravel/PHP 導入手順:Blade パーシャルとルート設計の落とし穴回避
articleHomebrew の Bottle vs ソースビルド比較検証:時間・サイズ・再現性の差をデータで解説
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来