- はじめに: なぜ今 GitHub Actions の高度な最適化なのか
- 1. マトリクスビルドの最適化
- 2. キャッシュ戦略の深掘り
- 3. Reusable Workflow と Composite Action
- 4. GitHub-hosted と Self-hosted Runner の比較
- 5. OIDC ベースのクラウド認証 (セキュリティハードニングの中核)
- 6. セキュリティハードニング総合ガイド
- 7. 実践的な統合ワークフロー: プロダクションパイプライン
- 8. 運用上の注意点と失敗事例
- 9. プロダクション CI/CD チェックリスト
- まとめ
- 参考資料
はじめに: なぜ今 GitHub Actions の高度な最適化なのか
2026年現在、GitHub Actions は世界で最も広く使われている CI/CD プラットフォームとして定着した。GitHub の公式発表によれば、Fortune 100 企業の 90% 以上が GitHub Actions を利用しており、毎日数百万のワークフローが実行されている。しかし、ほとんどのチームは基本的なビルド・テスト・デプロイのパイプラインにとどまっている。
プロダクション環境では単純なパイプラインだけでは足りない。マルチプラットフォーム対応、ビルド時間の最適化、シークレット管理、サプライチェーンセキュリティ (Supply Chain Security) まで考慮する必要がある。特に 2025 年末に発生した tj-actions/changed-files アクションのサプライチェーン攻撃は、GitHub Actions のセキュリティハードニングの重要性を改めて思い起こさせた。
本記事では、マトリクスビルド最適化、キャッシュ戦略、Reusable Workflow、OIDC ベースの認証、セキュリティハードニングまで、プロダクションレベルの CI/CD パイプラインを構築するための高度な手法を扱う。
一次資料および公式ドキュメント
| 出典 | 説明 |
|---|---|
| GitHub Actions 公式ドキュメント | ワークフロー構文、イベント、ランナーなど全体のリファレンス |
| GitHub Blog - Actions Security Best Practices | サプライチェーンセキュリティおよびアクションのベストプラクティス |
| OpenID Connect in GitHub Actions | OIDC トークンによるクラウド認証の公式ガイド |
| GitHub Actions - Caching Dependencies | 依存関係キャッシュ戦略の公式ガイド |
| Reusable Workflows | ワークフロー再利用パターンの公式ドキュメント |
| GitHub Actions Runner - Self-hosted | セルフホストランナーの設定・運用ガイド |
1. マトリクスビルドの最適化
1.1 基本のマトリクス戦略
マトリクス戦略 (Matrix Strategy) は、複数の環境の組み合わせに対してワークフローを並列に実行する中核機能だ。OS、言語バージョン、依存関係のバージョンなどを組み合わせてクロステストを自動化できる。
name: Matrix CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
max-parallel: 6
matrix:
os: [ubuntu-22.04, ubuntu-24.04, macos-14]
node-version: [20, 22]
include:
- os: ubuntu-24.04
node-version: 22
coverage: true
exclude:
- os: macos-14
node-version: 20
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test
- name: Upload coverage
if: ${{ matrix.coverage }}
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
上の設定で fail-fast: false は、1 つのマトリクス組み合わせが失敗しても残りの組み合わせの実行を続けさせる。こうすることで、どの環境で問題が起きているかを一度に把握できる。
1.2 マトリクスの動的生成
変更されたファイルに応じてテスト対象を動的に決めるパターンは、大規模なモノレポでビルド時間を大きく削減する。
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Detect changed services
id: set-matrix
run: |
CHANGED=$(git diff --name-only HEAD~1 HEAD | grep -oP '^services/\K[^/]+' | sort -u | jq -R . | jq -s .)
if [ "$CHANGED" = "[]" ]; then
echo "matrix={\"service\":[\"dummy\"]}" >> $GITHUB_OUTPUT
else
echo "matrix={\"service\":$CHANGED}" >> $GITHUB_OUTPUT
fi
build:
needs: detect-changes
if: ${{ fromJson(needs.detect-changes.outputs.matrix).service[0] != 'dummy' }}
runs-on: ubuntu-latest
strategy:
matrix: ${{ fromJson(needs.detect-changes.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- name: Build service
run: |
echo "Building ${{ matrix.service }}"
cd services/${{ matrix.service }}
docker build -t ${{ matrix.service }}:${{ github.sha }} .
1.3 マトリクス最適化のヒント
- max-parallel の制限: 同時実行数を制限し、GitHub-hosted ランナーの同時実行上限を超えないようにする
- fail-fast 戦略の選択: 素早いフィードバックが重要なら
true、全体の互換性確認が重要ならfalseを選ぶ - include で特殊ケースを追加: 特定の組み合わせにだけ追加の環境変数やステップを指定する
- exclude で不要な組み合わせを除去: サポートしない組み合わせ (例: Windows + ARM) を明示的に除外する
2. キャッシュ戦略の深掘り
2.1 キャッシュ種別の比較
| キャッシュ戦略 | 長所 | 短所 | 適した状況 |
|---|---|---|---|
| actions/cache | 汎用的で細かい制御が可能 | キー管理が手動 | カスタムビルドツール |
| setup-node の cache オプション | 設定が簡単で自動でキー生成 | Node.js 専用 | Node.js プロジェクト |
| Docker layer caching | イメージビルド時間の短縮 | キャッシュサイズ制限(10GB) | コンテナビルド |
| Artifact caching | ジョブ間のデータ受け渡し | ワークフロー内に限定 | ビルド成果物の共有 |
2.2 高度なキャッシュ設定
効果的なキャッシュキー戦略は、ビルド時間を 50~70% 短縮できる。
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 多段階キャッシュ復元戦略
- name: Cache node_modules
uses: actions/cache@v4
id: npm-cache
with:
path: |
node_modules
~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# Next.js ビルドキャッシュ
- name: Cache Next.js build
uses: actions/cache@v4
with:
path: .next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
${{ runner.os }}-nextjs-
# Gradle キャッシュ (Java/Kotlin プロジェクト)
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Install dependencies
if: steps.npm-cache.outputs.cache-hit != 'true'
run: npm ci
- name: Build
run: npm run build
2.3 Docker ビルドキャッシュの最適化
Docker イメージのビルドで BuildKit キャッシュを活用すると、ビルド時間を大きく短縮できる。
jobs:
docker-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push with cache
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
cache-from: type=gha と cache-to: type=gha,mode=max を使うと、GitHub Actions のキャッシュバックエンドを介して Docker レイヤーをキャッシュする。mode=max は中間レイヤーまですべてキャッシュし、後続ビルドの速度を最大化する。
2.4 キャッシュ管理の注意点
- キャッシュサイズの上限: リポジトリあたり合計 10GB までキャッシュを保存でき、超過すると古いキャッシュから削除される
- キャッシュの分離:
pull_requestイベントのキャッシュはデフォルトブランチのキャッシュを読めるが、デフォルトブランチには書き込めない - キャッシュの無効化: ロックファイル (package-lock.json、go.sum など) のハッシュをキャッシュキーに含め、依存関係が変わったら自動でキャッシュが更新されるようにする
- セキュリティ: キャッシュに機密情報 (トークン、クレデンシャル) が含まれないよう注意する。フォークされたリポジトリからキャッシュポイズニング (poisoning) 攻撃が可能な場合がある
3. Reusable Workflow と Composite Action
3.1 Reusable Workflow
組織全体で標準化された CI/CD パイプラインを共有するには Reusable Workflow を使う。呼び出す側では uses キーワードで別リポジトリのワークフローを参照する。
呼び出されるワークフロー (.github/workflows/reusable-deploy.yml):
name: Reusable Deploy Workflow
on:
workflow_call:
inputs:
environment:
description: 'Target environment'
required: true
type: string
image-tag:
description: 'Docker image tag'
required: true
type: string
secrets:
KUBE_CONFIG:
required: true
outputs:
deploy-url:
description: 'Deployed URL'
value: ${{ jobs.deploy.outputs.url }}
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
outputs:
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- name: Configure kubectl
run: |
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
- name: Deploy to Kubernetes
id: deploy
run: |
kubectl set image deployment/app \
app=${{ inputs.image-tag }} \
-n ${{ inputs.environment }}
kubectl rollout status deployment/app \
-n ${{ inputs.environment }} --timeout=300s
URL=$(kubectl get ingress app -n ${{ inputs.environment }} -o jsonpath='{.spec.rules[0].host}')
echo "url=https://$URL" >> $GITHUB_OUTPUT
呼び出す側のワークフロー:
name: Production Deploy
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- name: Build image
id: meta
run: |
TAG="ghcr.io/${{ github.repository }}:${{ github.ref_name }}"
docker build -t $TAG .
docker push $TAG
echo "tags=$TAG" >> $GITHUB_OUTPUT
deploy-staging:
needs: build
uses: my-org/shared-workflows/.github/workflows/reusable-deploy.yml@v2
with:
environment: staging
image-tag: ${{ needs.build.outputs.image-tag }}
secrets:
KUBE_CONFIG: ${{ secrets.STAGING_KUBE_CONFIG }}
deploy-production:
needs: [build, deploy-staging]
uses: my-org/shared-workflows/.github/workflows/reusable-deploy.yml@v2
with:
environment: production
image-tag: ${{ needs.build.outputs.image-tag }}
secrets:
KUBE_CONFIG: ${{ secrets.PROD_KUBE_CONFIG }}
3.2 Composite Action
Composite Action は複数のステップ (steps) を 1 つの再利用可能なアクションにまとめる。Reusable Workflow と違い、ジョブではなくステップ単位で再利用される。
# .github/actions/setup-and-test/action.yml
name: 'Setup and Test'
description: 'Install dependencies, lint, and test'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '22'
working-directory:
description: 'Working directory for the project'
required: false
default: '.'
outputs:
coverage-percentage:
description: 'Test coverage percentage'
value: ${{ steps.coverage.outputs.percentage }}
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
cache-dependency-path: ${{ inputs.working-directory }}/package-lock.json
- name: Install dependencies
shell: bash
working-directory: ${{ inputs.working-directory }}
run: npm ci
- name: Lint
shell: bash
working-directory: ${{ inputs.working-directory }}
run: npm run lint
- name: Test with coverage
shell: bash
working-directory: ${{ inputs.working-directory }}
run: npm test -- --coverage --coverageReporters=text-summary
- name: Extract coverage
id: coverage
shell: bash
run: |
COVERAGE=$(cat ${{ inputs.working-directory }}/coverage/coverage-summary.json | jq '.total.lines.pct')
echo "percentage=$COVERAGE" >> $GITHUB_OUTPUT
3.3 Reusable Workflow と Composite Action の比較
| 区分 | Reusable Workflow | Composite Action |
|---|---|---|
| 再利用の単位 | ジョブ全体 | ステップ単位 |
| 呼び出し方法 | jobs.*.uses | steps.*.uses |
| secrets の受け渡し | 明示的に渡す必要がある | 呼び出し元のコンテキストを継承 |
| ネスト呼び出し | 最大 4 段階 | 最大 10 段階 |
| ランナーの選択 | ワークフロー内で指定 | 呼び出し元のランナーを使用 |
| 適した用途 | パイプライン全体の標準化 | 共通の setup/teardown ステップ |
4. GitHub-hosted と Self-hosted Runner の比較
4.1 比較表
| 項目 | GitHub-hosted Runner | Self-hosted Runner |
|---|---|---|
| コスト | 分単位の課金 (Linux 0.008 USD/分) | インフラ費用のみ発生 |
| 環境 | 毎回クリーンな VM | 永続的な環境 (キャッシュ保持が可能) |
| カスタマイズ | 限定的 (プリインストール済みツールのみ) | 完全に自由 (GPU、特殊 HW) |
| セキュリティ | GitHub が管理 | 組織が自ら管理 |
| ネットワーク | パブリックインターネット | プライベートネットワークにアクセス可能 |
| スケーリング | 自動 | 手動またはオートスケーリング構成が必要 |
| 同時実行の上限 | プランにより異なる | 直接制御 |
| メンテナンス | 不要 | OS パッチ、ランナー更新が必要 |
4.2 Self-hosted Runner のセキュリティ上の注意点
Self-hosted Runner をパブリックリポジトリで使ってはならない。フォークされた PR から悪意あるコードがランナー上で実行されうるからだ。必ずプライベートリポジトリでのみ使うか、Actions Runner Controller (ARC) のようなツールでエフェメラル (ephemeral) ランナーを使うこと。
# Actions Runner Controller (ARC) - Kubernetes ベースのオートスケーリング セルフホストランナー
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
name: runner-deployment
spec:
replicas: 3
template:
spec:
repository: my-org/my-repo
ephemeral: true
labels:
- self-hosted
- linux
- x64
- gpu
resources:
limits:
nvidia.com/gpu: 1
memory: '16Gi'
requests:
cpu: '4'
memory: '8Gi'
---
apiVersion: actions.summerwind.dev/v1alpha1
kind: HorizontalRunnerAutoscaler
metadata:
name: runner-autoscaler
spec:
scaleTargetRef:
kind: RunnerDeployment
name: runner-deployment
minReplicas: 1
maxReplicas: 10
metrics:
- type: TotalNumberOfQueuedAndInProgressWorkflowRuns
repositoryNames:
- my-org/my-repo
5. OIDC ベースのクラウド認証 (セキュリティハードニングの中核)
5.1 なぜ OIDC なのか
従来の方式では、AWS Access Key や GCP Service Account Key といった長期クレデンシャルを GitHub Secrets に保存して使っていた。この方式にはいくつものリスクがある。
- シークレットが漏洩すると即座に悪用されうる
- キーローテーションを手動で管理しなければならない
- どのワークフローがどの権限を使ったのか追跡しにくい
OIDC (OpenID Connect) トークンを使うと、GitHub Actions はクラウドプロバイダーに短期トークンで認証する。長期クレデンシャルが不要になり、セキュリティが大きく向上する。
5.2 AWS の OIDC 認証設定
name: Deploy to AWS
on:
push:
branches: [main]
permissions:
id-token: write # OIDC トークンのリクエストに必要
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
role-session-name: github-actions-${{ github.run_id }}
aws-region: ap-northeast-2
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster production \
--service my-app \
--force-new-deployment
- name: Verify deployment
run: |
aws ecs wait services-stable \
--cluster production \
--services my-app
5.3 GCP の OIDC 認証設定
jobs:
deploy-gcp:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
service_account: 'deploy@my-project.iam.gserviceaccount.com'
- name: Deploy to Cloud Run
uses: google-github-actions/deploy-cloudrun@v2
with:
service: my-app
region: asia-northeast3
image: gcr.io/my-project/my-app:${{ github.sha }}
6. セキュリティハードニング総合ガイド
6.1 最小権限の原則の適用
GitHub Actions の GITHUB_TOKEN はデフォルトで広範な権限を持つ。本当に必要な権限だけを明示的に宣言しなければならない。
name: Secure Workflow
# グローバルレベルですべての権限を最小化
permissions: {}
on:
pull_request:
branches: [main]
jobs:
lint-and-test:
runs-on: ubuntu-latest
# ジョブレベルで必要な権限だけを宣言
permissions:
contents: read
checks: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
6.2 アクションバージョンのピン留め (Supply Chain Security)
タグ (v4) の代わりにコミット SHA を使ってアクションを固定する。タグは悪意を持って変更されうるが、コミット SHA は変更できない。
steps:
# 危険: タグは変更されうる
# - uses: actions/checkout@v4
# 安全: コミット SHA で固定
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '22'
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
GitHub が提供する Dependabot を活用すれば、この SHA ピン留めを自動で管理できる。.github/dependabot.yml に次を追加する。
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
groups:
actions:
patterns:
- '*'
6.3 シークレット管理のベストプラクティス
jobs:
secure-deploy:
runs-on: ubuntu-latest
environment: production # Environment Protection Rules を適用
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# シークレットを環境変数として公開するときの注意点
- name: Deploy with secrets
env:
# 個別のシークレットだけを明示的に渡す
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
run: |
# シークレットがログに出ないようマスクする
echo "::add-mask::$DATABASE_URL"
echo "::add-mask::$API_KEY"
./deploy.sh
6.4 フォーク PR のセキュリティ設定
フォークされたリポジトリから来る Pull Request はセキュリティリスクが高い。次の設定を推奨する。
pull_request_targetの代わりにpull_requestイベントを使う。pull_request_targetはベースリポジトリのシークレットにアクセスできるため危険だ- フォーク PR ではシークレットを使うジョブが実行されないよう条件を設定する
- Environment Protection Rules でプロダクションデプロイに手動承認を必須化する
jobs:
test:
# フォーク PR でも安全にテストを実行
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: npm ci
- run: npm test
deploy-preview:
# フォーク PR では実行しない
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Deploy preview
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: ./deploy-preview.sh
6.5 Artifact Attestation (ビルド出所の証明)
GitHub Actions は SLSA (Supply-chain Levels for Software Artifacts) フレームワークをサポートしており、ビルド成果物の出所を証明できる。
jobs:
build-with-attestation:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
attestations: write
packages: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build Docker image
run: |
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
- name: Push to GHCR
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: sha256:${{ steps.build.outputs.digest }}
push-to-registry: true
7. 実践的な統合ワークフロー: プロダクションパイプライン
ここまで扱ったすべての手法を統合した、プロダクションレベルの CI/CD パイプラインの例だ。
name: Production CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
# グローバルの最小権限
permissions:
contents: read
# 同一ブランチでの以前の実行をキャンセル
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run type-check
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm test -- --shard=${{ matrix.shard }}/4
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.shard }}
path: test-results/
retention-days: 7
security-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
build-and-push:
needs: [lint, test, security-scan]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
outputs:
image-digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Attest build provenance
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
deploy:
needs: build-and-push
runs-on: ubuntu-latest
environment: production
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: ap-northeast-2
- name: Deploy to EKS
run: |
aws eks update-kubeconfig --name production-cluster
kubectl set image deployment/app \
app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
kubectl rollout status deployment/app --timeout=300s
8. 運用上の注意点と失敗事例
8.1 よくある失敗事例
事例 1: キャッシュポイズニング攻撃
パブリックリポジトリで、フォーク PR が悪意ある依存関係をキャッシュに注入し、その後のビルドがそのキャッシュを使って悪意あるコードが実行された事例がある。対策として pull_request イベントでのキャッシュ書き込みを制限し、キャッシュキーにブランチ情報を含める。
事例 2: シークレットのログ露出
デバッグのために環境変数を出力する過程で、シークレットがログに露出した事例だ。GitHub Actions は登録済みのシークレットを自動的にマスクするが、シークレットを加工 (base64 エンコードなど) してから出力するとマスクが回避される。echo "::add-mask::" コマンドで加工後の値も必ずマスクしなければならない。
事例 3: サードパーティアクションのタグ改ざん
2025 年末に発生した tj-actions/changed-files のサプライチェーン攻撃では、攻撃者がアクションのタグを悪意あるコードを含むコミットに付け替えた。タグではなくコミット SHA を使っていれば影響を受けなかったはずだ。
事例 4: concurrency 未設定による重複デプロイ
立て続けにプッシュしたときに複数のデプロイジョブが同時に実行され、ロールバックが複雑になった事例だ。concurrency グループを設定し、同一環境への同時デプロイを防ぐ必要がある。
8.2 コスト最適化のヒント
- concurrency の設定: 同一ブランチの以前の実行を自動でキャンセルし、不要な実行時間を削減する
- path フィルタ: 関連するファイルが変更されたときだけワークフローをトリガーする
- テストのシャーディング: テストを並列に分割実行し、全体の時間を短縮する
- Larger Runner の活用: GitHub-hosted Larger Runner を使うと、ビルド時間の短縮によって全体のコストが下がることがある
9. プロダクション CI/CD チェックリスト
セキュリティ
- グローバルの
permissions: {}で最小権限の原則を適用 - すべてのサードパーティアクションをコミット SHA でピン留め
- Dependabot でアクションの更新を自動化
- OIDC トークンでクラウド認証 (長期クレデンシャルを排除)
- フォーク PR からのシークレットアクセスを遮断
- Environment Protection Rules でプロダクションデプロイの手動承認を必須化
-
pull_request_targetを使う場合はセキュリティレビューを完了
パフォーマンス
- 依存関係キャッシュの設定 (actions/cache または setup-* の組み込みキャッシュ)
- Docker BuildKit キャッシュの活用 (type=gha)
- マトリクスビルドで並列テストを実行
- concurrency 設定で重複実行を防止
- path フィルタで不要なワークフロー実行を排除
- テストのシャーディングでテスト時間を分散
メンテナンス
- Reusable Workflow で共通パターンを標準化
- Composite Action で繰り返しステップをモジュール化
- ワークフローファイルに十分なコメントを追加
- 失敗通知の設定 (Slack、Email など)
- 定期的なワークフロー監査 (未使用シークレット、不要な権限の確認)
サプライチェーンセキュリティ
- ビルド成果物に Artifact Attestation を適用
- SLSA フレームワークのレベル確認と目標設定
- コンテナイメージの署名 (Sigstore/Cosign)
- SBOM (Software Bill of Materials) の生成と管理
まとめ
GitHub Actions の高度な機能を活用すれば、単純なビルド・テストの自動化を超えて、プロダクションレベルのセキュリティと効率性を備えた CI/CD パイプラインを構築できる。特に次の 3 点を核として覚えておきたい。
- セキュリティ優先: 最小権限の原則、SHA ピン留め、OIDC 認証は選択ではなく必須だ
- キャッシュ戦略: 適切なキャッシュ設定だけでもビルド時間を 50% 以上短縮できる
- モジュール化: Reusable Workflow と Composite Action で保守性を高め、組織全体の標準を確立する
CI/CD パイプラインは一度作って終わりではなく、継続的に改善しセキュリティを強化していく生きたシステムだ。定期的にチェックリストを点検し、新しいセキュリティ脅威に対応することが重要だ。