GitHub Actions上級テクニック完全ガイド2026 - マトリクスビルド、再利用ワークフロー、カスタムアクション、セキュリティ、キャッシュ最適化
GitHub Actions上級テクニック完全ガイド2026
GitHub Actionsは進化を続け、2026年時点でさらに強力な機能が追加されています。本記事では、マトリクスビルド、再利用可能ワークフロー、カスタムアクションなど、上級テクニックを徹底解説します。
目次
- マトリクスビルド完全攻略
- 再利用可能ワークフロー
- カスタムアクション作成
- セキュリティベストプラクティス
- キャッシュ最適化
- 動的ワークフロー生成
- デバッグとトラブルシューティング
- パフォーマンス最適化
- 実践的なワークフロー例
マトリクスビルド完全攻略
基本的なマトリクスビルド
name: Matrix Build
on: [push]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [18, 20, 22]
# 3 OS × 3 Node.js = 9つのジョブが並列実行
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
includeとexcludeの活用
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [18, 20, 22]
# 特定の組み合わせを除外
exclude:
- os: macos-latest
node-version: 18
- os: windows-latest
node-version: 18
# 特定の組み合わせを追加
include:
# カナリアビルド
- os: ubuntu-latest
node-version: 23-nightly
experimental: true
# 特別な設定
- os: ubuntu-latest
node-version: 20
coverage: true
continue-on-error: ${{ matrix.experimental == true }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Upload coverage
if: matrix.coverage == true
uses: codecov/codecov-action@v3
動的マトリクス
jobs:
prepare-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- name: Generate matrix
id: set-matrix
run: |
# 変更されたパッケージを検出
PACKAGES=$(node scripts/detect-changed-packages.js)
echo "matrix=$PACKAGES" >> $GITHUB_OUTPUT
test:
needs: prepare-matrix
runs-on: ubuntu-latest
strategy:
matrix: ${{ fromJSON(needs.prepare-matrix.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- name: Test ${{ matrix.package }}
run: npm test --workspace=${{ matrix.package }}
// scripts/detect-changed-packages.js
const { execSync } = require('child_process');
const fs = require('fs');
// 変更されたファイルを取得
const changedFiles = execSync('git diff --name-only HEAD~1')
.toString()
.split('\n')
.filter(Boolean);
// パッケージディレクトリを特定
const packages = new Set();
for (const file of changedFiles) {
if (file.startsWith('packages/')) {
const pkg = file.split('/')[1];
packages.add(pkg);
}
}
// マトリクス形式で出力
const matrix = {
package: Array.from(packages),
};
console.log(JSON.stringify(matrix));
fail-fastとmax-parallel
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
# 1つ失敗したら全て停止しない
fail-fast: false
# 最大3つまで並列実行
max-parallel: 3
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm test
再利用可能ワークフロー
再利用可能ワークフローの定義
# .github/workflows/reusable-test.yml
name: Reusable Test Workflow
on:
workflow_call:
inputs:
node-version:
required: true
type: string
coverage:
required: false
type: boolean
default: false
working-directory:
required: false
type: string
default: '.'
outputs:
test-result:
description: "Test result status"
value: ${{ jobs.test.outputs.result }}
secrets:
CODECOV_TOKEN:
required: false
jobs:
test:
runs-on: ubuntu-latest
outputs:
result: ${{ steps.test.outcome }}
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
id: test
run: |
if [ "${{ inputs.coverage }}" = "true" ]; then
npm run test:coverage
else
npm test
fi
- name: Upload coverage
if: inputs.coverage && success()
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
working-directory: ${{ inputs.working-directory }}
再利用可能ワークフローの呼び出し
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
# フロントエンドのテスト
test-frontend:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '20'
coverage: true
working-directory: './frontend'
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# バックエンドのテスト
test-backend:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '20'
coverage: true
working-directory: './backend'
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# マトリクスで複数バージョンテスト
test-matrix:
strategy:
matrix:
node-version: [18, 20, 22]
uses: ./.github/workflows/reusable-test.yml
with:
node-version: ${{ matrix.node-version }}
coverage: false
ネストした再利用可能ワークフロー
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy Workflow
on:
workflow_call:
inputs:
environment:
required: true
type: string
jobs:
# まずテストを実行
test:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '20'
coverage: true
secrets: inherit
# テスト成功後にデプロイ
deploy:
needs: test
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- name: Deploy to ${{ inputs.environment }}
run: |
echo "Deploying to ${{ inputs.environment }}"
npm run deploy:${{ inputs.environment }}
カスタムアクション作成
JavaScriptアクション
# action.yml
name: 'Version Bumper'
description: 'Automatically bump package version'
author: 'Your Name'
inputs:
bump-type:
description: 'Version bump type (major, minor, patch)'
required: true
default: 'patch'
github-token:
description: 'GitHub token for creating commits'
required: true
outputs:
new-version:
description: 'The new version number'
old-version:
description: 'The old version number'
runs:
using: 'node20'
main: 'dist/index.js'
branding:
icon: 'arrow-up'
color: 'blue'
// src/index.ts
import * as core from '@actions/core';
import * as github from '@actions/github';
import { execSync } from 'child_process';
import fs from 'fs';
async function run() {
try {
// 入力を取得
const bumpType = core.getInput('bump-type', { required: true });
const githubToken = core.getInput('github-token', { required: true });
// package.jsonを読み込み
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const oldVersion = packageJson.version;
core.info(`Current version: ${oldVersion}`);
// バージョンをバンプ
execSync(`npm version ${bumpType} --no-git-tag-version`, {
stdio: 'inherit',
});
// 新しいバージョンを取得
const updatedPackageJson = JSON.parse(
fs.readFileSync('package.json', 'utf8')
);
const newVersion = updatedPackageJson.version;
core.info(`New version: ${newVersion}`);
// GitHubにコミット
const octokit = github.getOctokit(githubToken);
const { owner, repo } = github.context.repo;
const content = fs.readFileSync('package.json', 'utf8');
const encodedContent = Buffer.from(content).toString('base64');
// ファイルのSHAを取得
const { data: fileData } = await octokit.rest.repos.getContent({
owner,
repo,
path: 'package.json',
});
// ファイルを更新
await octokit.rest.repos.createOrUpdateFileContents({
owner,
repo,
path: 'package.json',
message: `chore: bump version to ${newVersion}`,
content: encodedContent,
sha: Array.isArray(fileData) ? '' : fileData.sha,
branch: github.context.ref.replace('refs/heads/', ''),
});
// 出力を設定
core.setOutput('new-version', newVersion);
core.setOutput('old-version', oldVersion);
core.summary
.addHeading('Version Bump Summary')
.addTable([
[
{ data: 'Old Version', header: true },
{ data: 'New Version', header: true },
{ data: 'Bump Type', header: true },
],
[oldVersion, newVersion, bumpType],
])
.write();
} catch (error) {
if (error instanceof Error) {
core.setFailed(error.message);
}
}
}
run();
Dockerアクション
# action.yml
name: 'Security Scanner'
description: 'Scan for security vulnerabilities'
inputs:
severity:
description: 'Minimum severity level'
required: false
default: 'MEDIUM'
outputs:
vulnerabilities-found:
description: 'Number of vulnerabilities found'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.severity }}
# Dockerfile
FROM node:20-alpine
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
RUN npm install -g npm-audit-html
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
# entrypoint.sh
SEVERITY=$1
echo "Running security scan with severity: $SEVERITY"
# npm auditを実行
npm audit --audit-level=$SEVERITY --json > audit-results.json
# 脆弱性の数をカウント
VULNERABILITIES=$(jq '.metadata.vulnerabilities.total' audit-results.json)
echo "vulnerabilities-found=$VULNERABILITIES" >> $GITHUB_OUTPUT
# HTMLレポートを生成
npm-audit-html --output audit-report.html
# アーティファクトとしてアップロード
echo "::notice::Security scan completed. Found $VULNERABILITIES vulnerabilities."
exit 0
Compositeアクション
# action.yml
name: 'Setup Project'
description: 'Setup Node.js, install dependencies, and configure cache'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '20'
package-manager:
description: 'Package manager (npm, yarn, pnpm)'
required: false
default: 'npm'
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: ${{ inputs.package-manager }}
- name: Install pnpm
if: inputs.package-manager == 'pnpm'
uses: pnpm/action-setup@v2
with:
version: 8
- name: Install dependencies (npm)
if: inputs.package-manager == 'npm'
shell: bash
run: npm ci
- name: Install dependencies (yarn)
if: inputs.package-manager == 'yarn'
shell: bash
run: yarn install --frozen-lockfile
- name: Install dependencies (pnpm)
if: inputs.package-manager == 'pnpm'
shell: bash
run: pnpm install --frozen-lockfile
- name: Display versions
shell: bash
run: |
echo "Node.js version: $(node --version)"
echo "Package manager: ${{ inputs.package-manager }}"
if [ "${{ inputs.package-manager }}" = "npm" ]; then
echo "npm version: $(npm --version)"
elif [ "${{ inputs.package-manager }}" = "yarn" ]; then
echo "Yarn version: $(yarn --version)"
elif [ "${{ inputs.package-manager }}" = "pnpm" ]; then
echo "pnpm version: $(pnpm --version)"
fi
セキュリティベストプラクティス
シークレット管理
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
# 環境変数としてシークレットを使用
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
# シークレットを直接echoしない
echo "Deploying with credentials..."
deploy.sh
# マスクされた出力
- name: Mask sensitive data
run: |
# 動的にシークレットをマスク
echo "::add-mask::$MY_SECRET_VALUE"
echo "The secret is: $MY_SECRET_VALUE"
OIDCトークンによる認証
jobs:
deploy-to-aws:
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC トークン取得に必要
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
# シークレットキー不要でAWSにアクセス可能
- name: Deploy to S3
run: |
aws s3 sync ./build s3://my-bucket/
依存関係の検証
jobs:
security-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 依存関係の脆弱性スキャン
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
# npm audit
- name: Run npm audit
run: |
npm audit --audit-level=moderate
continue-on-error: true
# Dependabot alerts check
- name: Check Dependabot alerts
uses: actions/github-script@v7
with:
script: |
const alerts = await github.rest.dependabot.listAlertsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
});
if (alerts.data.length > 0) {
core.warning(`Found ${alerts.data.length} open Dependabot alerts`);
}
コード署名
jobs:
build-and-sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build artifact
run: npm run build
- name: Sign artifact
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
run: |
echo "$SIGNING_KEY" | base64 -d > signing-key.pem
openssl dgst -sha256 -sign signing-key.pem \
-out build/signature.sig build/app.js
rm signing-key.pem
- name: Upload signed artifact
uses: actions/upload-artifact@v4
with:
name: signed-build
path: build/
キャッシュ最適化
依存関係のキャッシュ
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Node.js setup with automatic caching
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# カスタムキャッシュ
- name: Cache node modules
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
ビルドキャッシュ
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Next.jsビルドキャッシュ
- name: Cache Next.js build
uses: actions/cache@v4
with:
path: |
~/.npm
${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- name: Build
run: npm run build
Dockerレイヤーキャッシュ
jobs:
docker-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: myapp:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
# キャッシュの移動(古いキャッシュを削除)
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
高度なキャッシュ戦略
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 複数のキャッシュキーでフォールバック
- name: Multi-level cache
uses: actions/cache@v4
with:
path: |
node_modules
.cache
key: v1-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js') }}
restore-keys: |
v1-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-
v1-${{ runner.os }}-
# キャッシュヒット時はスキップ
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
# 条件付きキャッシュ保存
- name: Save cache
if: success()
uses: actions/cache/save@v4
with:
path: test-results/
key: test-results-${{ github.sha }}
動的ワークフロー生成
ジョブの動的生成
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.packages.outputs.list }}
steps:
- uses: actions/checkout@v4
- name: Get changed packages
id: packages
run: |
PACKAGES=$(node -e "
const { readdirSync } = require('fs');
const packages = readdirSync('packages', { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name);
console.log(JSON.stringify(packages));
")
echo "list=$PACKAGES" >> $GITHUB_OUTPUT
test:
needs: prepare
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJSON(needs.prepare.outputs.packages) }}
steps:
- uses: actions/checkout@v4
- name: Test ${{ matrix.package }}
run: npm test --workspace=packages/${{ matrix.package }}
条件付きジョブ実行
jobs:
changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
docs: ${{ steps.filter.outputs.docs }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'
docs:
- 'docs/**'
test-frontend:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Testing frontend..."
test-backend:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Testing backend..."
build-docs:
needs: changes
if: needs.changes.outputs.docs == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Building docs..."
デバッグとトラブルシューティング
デバッグログの有効化
jobs:
debug-job:
runs-on: ubuntu-latest
steps:
- name: Enable debug logging
run: |
echo "ACTIONS_STEP_DEBUG=true" >> $GITHUB_ENV
echo "ACTIONS_RUNNER_DEBUG=true" >> $GITHUB_ENV
- name: Debug information
run: |
echo "Runner OS: ${{ runner.os }}"
echo "GitHub event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJSON(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Dump job context
env:
JOB_CONTEXT: ${{ toJSON(job) }}
run: echo "$JOB_CONTEXT"
SSH デバッグ
jobs:
debug:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# エラー時にSSHアクセスを有効化
- name: Setup tmate session
if: failure()
uses: mxschmitt/action-tmate@v3
timeout-minutes: 15
ステップサマリー
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and create summary
run: |
npm run build
# ビルド結果をサマリーに追加
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Build time: $(date)" >> $GITHUB_STEP_SUMMARY
echo "- Bundle size: $(du -h dist/bundle.js | cut -f1)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Files generated" >> $GITHUB_STEP_SUMMARY
ls -lh dist/ >> $GITHUB_STEP_SUMMARY
パフォーマンス最適化
並列実行の最適化
jobs:
# 依存関係のないジョブは並列実行
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
typecheck:
runs-on: ubuntu-latest
steps:
- run: npm run typecheck
test-unit:
runs-on: ubuntu-latest
steps:
- run: npm run test:unit
test-integration:
runs-on: ubuntu-latest
steps:
- run: npm run test:integration
# すべて成功したらビルド
build:
needs: [lint, typecheck, test-unit, test-integration]
runs-on: ubuntu-latest
steps:
- run: npm run build
アーティファクトの最適化
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build
# 必要なファイルのみアップロード
- uses: actions/upload-artifact@v4
with:
name: build
path: |
dist/
!dist/**/*.map
retention-days: 7 # 保存期間を短縮
compression-level: 9 # 最大圧縮
実践的なワークフロー例
モノレポCI/CD
name: Monorepo CI/CD
on:
push:
branches: [main]
pull_request:
jobs:
changes:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.filter.outputs.changes }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
web:
- 'packages/web/**'
api:
- 'packages/api/**'
shared:
- 'packages/shared/**'
test:
needs: changes
if: needs.changes.outputs.packages != '[]'
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJSON(needs.changes.outputs.packages) }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Test ${{ matrix.package }}
run: npm test --workspace=packages/${{ matrix.package }}
deploy:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJSON(needs.changes.outputs.packages) }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Deploy ${{ matrix.package }}
run: npm run deploy --workspace=packages/${{ matrix.package }}
まとめ
GitHub Actions の上級テクニックをマスターすることで、効率的で保守性の高いCI/CDパイプラインを構築できます。
重要ポイント:
- マトリクスビルド: 複数環境での並列テスト
- 再利用可能ワークフロー: DRY原則の適用
- カスタムアクション: 共通処理の抽象化
- セキュリティ: シークレット管理とOIDC認証
- キャッシュ: ビルド時間の短縮
- 最適化: 並列実行とリソース効率化
これらのテクニックを活用して、プロジェクトに最適なCI/CDワークフローを構築してください。