GitHub ActionsでCI/CDをセットアップする方法

Table of Contents
私はCI/CDへの導入が遅れていました。何年もの間、私はローカルでテストを実行し、指を交差させて(うまくいくことを祈って)プッシュしていました。それは問題なく機能していましたが、ある日、ビルドを壊すコミットをプッシュしてしまい、どの依存関係の変更が原因だったのかを突き止めるのに1時間も費やしました。
GitHub Actionsは、パブリックリポジトリでは無料で、プライベートリポジトリでは私が知る限り最も安価な選択肢です。Next.jsアプリからPython API、Goサービスまで、さまざまなプロジェクトで実行した結果、私がたどり着いたセットアップがこれです。
なぜ他の選択肢ではなくGitHub Actionsなのか?
CIツールを選ぶ前に:GitHub Actionsはリポジトリ上で直接実行され、接続するサードパーティサービスは不要です。Jenkinsはサーバーが必要です。CircleCIは、一部のユースケースではより良い無料枠を提供しています。Travis CIは有料のみになりました。GitLabをすでに使用しているなら、GitLab CIは素晴らしい選択肢です。
GitHubでホストされているプロジェクトの場合、Actionsは利便性で優れています。Webhookの設定も、トークンの管理も不要で、トリガーはコードの隣で定義されます。
初めてのワークフロー
GitHub Actionsのワークフローは、YAMLファイルとして.github/workflows/に配置されます。.github/workflows/ci.ymlを作成してください。
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test
- run: npm run build
これをプッシュすると、次のコミットでワークフローがトリガーされます。すべてのPRに緑色のチェックマークまたは赤色のXが付き、壊れたコードをマージすることは二度とありません。
npm ci vs npm install
CIではnpm installではなくnpm ciを使用してください。npm ciはより厳格です。package-lock.jsonに記載されているものを正確にインストールし、不一致があれば失敗し、ロックファイルを更新することはありません。これにより、CI環境がローカルでテストしたものと一致します。
キャッシュで高速化
上記のcache: 'npm'行は、package-lock.jsonのハッシュに基づいて、実行間でnode_modulesをキャッシュします。これがないと、ワークフローが実行されるたびに、すべての依存関係がゼロから再インストールされます。これは、依存関係の数に応じて、実行ごとに60〜120秒かかります。
キャッシュを使用すると、キャッシュヒットにより5〜10秒に短縮されます。
他のエコシステムの場合:
# Python (pip)
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
# Python (uv)
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
# Go
- uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
# Rust
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
キャッシュはロックファイルのハッシュに基づいてキーが設定されます。依存関係が変更されると、キャッシュはミスして再構築されます。変更されない場合、キャッシュはヒットし、CIは高速になります。
並行テスト
大規模なテストスイートの場合、テストを複数のランナーに分割します。
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test -- --shard=${{ matrix.shard }}/4
これにより、4つのジョブが同時に実行され、それぞれがテストの4分の1を処理します。合計テスト時間は4倍から約1倍に短縮されます。
Playwrightは--shardでこれをネイティブにサポートしています。Jestの場合は--testPathPatternまたはjest-runner-groupsを使用してください。pytestの場合はpytest-splitを使用してください。
自動デプロイ
CI(テスト)とCD(デプロイ)を分離します。これらは異なる失敗セマンティクスを持っています。テストの失敗はPRをブロックすべきですが、デプロイの失敗は他のPRをブロックすべきではありません。
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
- name: Deploy to Vercel
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
run: npx vercel --prod --token $VERCEL_TOKEN
ハードコードされたトークンではなくシークレットを使用する
APIキーやデプロイトークンをYAMLファイルに直接記述しないでください。GitHub → Settings → Secrets and variables → Actionsに保存し、${{ secrets.MY_TOKEN }}として参照してください。シークレットは保存時に暗号化され、ログではマスクされ、フォークされたPRには決して公開されません。
テスト成功時のみデプロイ
needsキーワードを使用してCIとCDを連結します。
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- run: npx vercel --prod --token ${{ secrets.VERCEL_TOKEN }}
deployジョブは、testが成功し、かつmainブランチにいる場合にのみ実行されます。
環境変数とシークレット
GitHub Actionsで設定を扱うための3つの階層:
1. YAMLにハードコード — 機密性のない、普遍的な値のみ:
env:
NODE_ENV: production
PORT: 3000
2. リポジトリシークレット — 機密性の高い値(APIキー、トークン):
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
3. 環境シークレット — デプロイ環境ごとに異なる値(ステージング vs プロダクション):
jobs:
deploy:
environment: production # uses the "production" environment's secrets
steps:
- run: deploy.sh
env:
API_URL: ${{ secrets.API_URL }} # production-specific value
環境シークレットには承認ゲートが必要です。Settings → Environments → protection rulesで必要なレビュー担当者を設定します。誤った本番デプロイを防ぐのに役立ちます。
苦労して学んだこと
メインブランチだけでなく、すべてのPRでCIを実行する。 PRブランチでテストの失敗を検出する方が、マージ後に検出するよりもはるかに安価です。すべてのワークフローにpull_request: branches: [main]を追加してください。
CIとCDを分離する。 CIワークフローはプッシュごとに実行され、5分以内に完了すべきです。CDワークフローはデプロイを行い、より時間がかかる場合があります。これらを混同すると、遅いデプロイがPRのフィードバックループをブロックしてしまいます。
マトリックスビルドは請求額を増やす。 マトリックス戦略で複数のNodeバージョンやOSにわたってテストすると、ランナーの時間が何倍にもなります。私は、クロスバージョン互換性が実際に重要なライブラリの場合にのみ使用します。アプリの場合、1つのターゲット環境で十分です。
# Only do this for libraries, not apps
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest, macos-latest]
これはプッシュごとに9つのランナーです。それぞれ2分とすると、合計18分の計算時間になります。
利用時間を監視する。 GitHub Actionsは、無料プランのプライベートリポジトリに対して月2,000分の無料枠を提供しています。マトリックスビルドを使用し、1日10回プッシュするプロジェクトでは、1週間で使い切ってしまう可能性があります。
fail-fast: trueで高速に失敗する(マトリックスビルドのデフォルト)。1つのマトリックスジョブが失敗すると、GitHubは他のジョブをキャンセルします。根本的なエラーがある場合に時間を節約できます。
Dockerレイヤーには明示的にactions/cacheを使用する。
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
context: .
cache-from: type=gha
cache-to: type=gha,mode=max
これがないと、Dockerは実行ごとにイメージ全体をゼロから再構築します。
完全な本番環境対応ワークフロー
以下は、私がNext.jsプロジェクトで使用している完全なワークフローです。
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run type-check
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: .next/
retention-days: 1
主な機能:
concurrencyは、新しいプッシュが到着したときに進行中の実行をキャンセルします。これにより、活発な開発中のキューの蓄積を防ぎます。lintとtestは並行して実行され、buildは両方の完了を待ちます。- カバレッジレポートはプッシュごとにCodecovに送信されます。
- ビルド成果物は1日間アップロードされます(再構築せずにデプロイジョブが利用するのに便利です)。
失敗したワークフローのデバッグ
ワークフローが失敗し、ログが不明瞭な場合:
デバッグログを有効にするには、リポジトリシークレットACTIONS_STEP_DEBUG=trueを設定します。これにより、すべてのステップから詳細な出力がダンプされます。
tmateを使用してランナーにSSH接続する:
- uses: mxschmitt/action-tmate@v3
if: ${{ failure() }}
これにより、ワークフローが一時停止し、ステップが失敗したときに正確なランナー環境へのSSH接続が提供されます。環境固有の問題をデバッグするのに非常に貴重です。
環境の詳細を出力する:
- run: node --version && npm --version && env | sort
こちらもおすすめ
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

GitHubActionsセルフホストランナーのセキュリティ強化
ActionsRunnerController(ARC)、ネットワーク分離、rootlessコンテナ、短命OIDCトークンを使用して、セルフホスト型GitHubActionsランナーを強化します。
Read more
2026年版Playwrightの主要代替ツール:Cypress、WebdriverIO、Vitest、Puppeteerを比較
2026年におけるPlaywrightの主要代替ツールであるCypress、WebdriverIO、Vitest、Puppeteerを、実証済みの本番環境での使用例を交えて網羅的に比較解説します。
Read more
タイトル:CI/CDパイプラインにおけるAIエージェントの未来
概要:自律型AIエージェントがログトリアージの自動化、テスト失敗の自己修復、プルリクエストレビューワークフローを通じてCI/CDパイプラインをどのように近代化しているかを探ります。
Read more