GitHub Actions CI/CD完全ガイド - 自動化パイプラインの構築

intermediate | 75分 で読める | 2025.12.02

GitHub Actionsは、GitHubに統合されたCI/CD(継続的インテグレーション/継続的デリバリー)プラットフォームです。コードのプッシュやプルリクエストをトリガーに、テスト、ビルド、デプロイを自動化できます。本記事では、実践的なワークフローの構築方法を詳しく解説します。

この記事で学ぶこと

  1. GitHub Actionsの基本概念
  2. ワークフローファイルの構文
  3. Node.js/Pythonプロジェクトのテスト自動化
  4. キャッシュによる高速化
  5. マトリックスビルド
  6. 本番環境へのデプロイ
  7. セキュリティベストプラクティス

基本概念

GitHub Actionsの構成要素

flowchart TB
    subgraph Workflow["Workflow(ワークフロー)<br/>.github/workflows/ci.yml"]
        subgraph TestJob["Job(ジョブ): test"]
            Step1["Step 1: actions/checkout@v4"]
            Step2["Step 2: actions/setup-node@v4"]
            Step3["Step 3: npm install"]
            Step4["Step 4: npm test"]
            Step1 --> Step2 --> Step3 --> Step4
        end

        subgraph DeployJob["Job(ジョブ): deploy"]
            DeploySteps["Deploy steps..."]
        end

        TestJob -->|depends on| DeployJob
    end
用語説明
WorkflowYAMLで定義された自動化プロセス全体
Jobワークフロー内の実行単位。同じランナーで実行される
Stepジョブ内の個々のタスク。アクションまたはシェルコマンド
Action再利用可能なタスク単位(actions/checkout等)
Runnerワークフローを実行するサーバー

最初のワークフロー

ファイルの配置

project/
└── .github/
    └── workflows/
        ├── ci.yml          # テスト・ビルド用
        ├── deploy.yml      # デプロイ用
        └── release.yml     # リリース用

基本的なCIワークフロー

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

トリガー(on)の詳細

各種トリガー

on:
  # プッシュ時
  push:
    branches:
      - main
      - 'release/**'        # パターンマッチ
    paths:
      - 'src/**'            # 特定パスの変更時のみ
      - '!src/**/*.md'      # マークダウンは除外
    tags:
      - 'v*'                # タグ付け時

  # プルリクエスト
  pull_request:
    branches: [main]
    types: [opened, synchronize, reopened]

  # 手動実行
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deployment environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

  # スケジュール実行(UTC)
  schedule:
    - cron: '0 0 * * 1'     # 毎週月曜 00:00 UTC

  # 他のワークフローからの呼び出し
  workflow_call:
    inputs:
      config-path:
        required: true
        type: string

条件付き実行

jobs:
  deploy:
    # mainブランチへのプッシュ時のみ実行
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      # ...

  notify:
    # 前のジョブが失敗した場合のみ実行
    if: failure()
    needs: [test, deploy]
    runs-on: ubuntu-latest
    steps:
      # ...

環境変数とシークレット

環境変数の設定

# ワークフローレベル
env:
  NODE_ENV: production
  CI: true

jobs:
  build:
    runs-on: ubuntu-latest
    # ジョブレベル
    env:
      DATABASE_URL: postgres://localhost/test

    steps:
      - name: Build with environment
        # ステップレベル
        env:
          API_KEY: ${{ secrets.API_KEY }}
        run: |
          echo "Node env: $NODE_ENV"
          echo "Building..."

シークレットの使用

steps:
  - name: Deploy to production
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    run: |
      aws s3 sync ./dist s3://my-bucket

  # GITHUB_TOKENは自動的に利用可能
  - name: Create release
    uses: actions/create-release@v1
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Environment Secrets

jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com

    steps:
      # productionの環境変数・シークレットにアクセス可能
      - name: Deploy
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: ./deploy.sh

キャッシュ戦略

npm キャッシュ

steps:
  - uses: actions/checkout@v4

  - uses: actions/setup-node@v4
    with:
      node-version: '20'
      cache: 'npm'  # 自動キャッシュ

  - run: npm ci

カスタムキャッシュ

steps:
  - name: Cache node modules
    id: cache-npm
    uses: actions/cache@v4
    with:
      path: ~/.npm
      key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
      restore-keys: |
        ${{ runner.os }}-npm-

  - name: Install dependencies
    if: steps.cache-npm.outputs.cache-hit != 'true'
    run: npm ci

  # ビルド成果物のキャッシュ
  - name: Cache build output
    uses: actions/cache@v4
    with:
      path: |
        .next/cache
        dist
      key: ${{ runner.os }}-build-${{ github.sha }}
      restore-keys: |
        ${{ runner.os }}-build-

依存関係のキャッシュ(複数言語)

steps:
  # Python
  - uses: actions/setup-python@v5
    with:
      python-version: '3.12'
      cache: 'pip'

  # Go
  - uses: actions/setup-go@v5
    with:
      go-version: '1.22'
      cache: true

  # Rust
  - uses: actions/cache@v4
    with:
      path: |
        ~/.cargo/bin/
        ~/.cargo/registry/index/
        ~/.cargo/registry/cache/
        target/
      key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

マトリックスビルド

複数バージョンでのテスト

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]
        include:
          - node-version: 20
            is-primary: true
      fail-fast: false  # 1つ失敗しても他は継続

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - run: npm ci
      - run: npm test

      # プライマリバージョンでのみカバレッジレポート
      - name: Upload coverage
        if: matrix.is-primary
        uses: codecov/codecov-action@v4

クロスプラットフォームテスト

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [18, 20]
        exclude:
          # Windows + Node 18の組み合わせをスキップ
          - os: windows-latest
            node-version: 18

    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

ジョブ間の依存関係

順次実行

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run lint

  test:
    needs: lint  # lintが成功後に実行
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

  build:
    needs: [lint, test]  # 複数ジョブに依存
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build

アーティファクトの受け渡し

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 1

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Deploy
        run: ./deploy.sh dist/

実践的なワークフロー例

Node.jsプロジェクトの完全なCI/CD

# .github/workflows/ci-cd.yml
name: CI/CD

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '20'

jobs:
  # コード品質チェック
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - run: npm ci

      - name: Type check
        run: npm run type-check

      - name: Lint
        run: npm run lint

      - name: Format check
        run: npm run format:check

  # テスト
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - run: npm ci

      - name: Run tests
        env:
          DATABASE_URL: postgres://test:test@localhost:5432/test
        run: npm run test:coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  # ビルド
  build:
    needs: [quality, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - run: npm ci
      - run: npm run build

      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/

  # ステージングデプロイ(PRマージ時)
  deploy-staging:
    needs: build
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.myapp.com

    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build
          path: dist/

      - name: Deploy to staging
        env:
          DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}
        run: |
          echo "Deploying to staging..."
          # デプロイスクリプト

  # 本番デプロイ(手動承認後)
  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com

    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build
          path: dist/

      - name: Deploy to production
        env:
          DEPLOY_KEY: ${{ secrets.PRODUCTION_DEPLOY_KEY }}
        run: |
          echo "Deploying to production..."
          # デプロイスクリプト

Docker イメージのビルドとプッシュ

name: Docker Build

on:
  push:
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

再利用可能なワークフロー

呼び出し可能ワークフローの定義

# .github/workflows/reusable-test.yml
name: Reusable Test Workflow

on:
  workflow_call:
    inputs:
      node-version:
        required: false
        type: string
        default: '20'
    secrets:
      npm-token:
        required: false

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'npm'
          registry-url: 'https://registry.npmjs.org'

      - run: npm ci
        env:
          NODE_AUTH_TOKEN: ${{ secrets.npm-token }}

      - run: npm test

再利用可能ワークフローの呼び出し

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  call-test:
    uses: ./.github/workflows/reusable-test.yml
    with:
      node-version: '20'
    secrets:
      npm-token: ${{ secrets.NPM_TOKEN }}

セキュリティベストプラクティス

依存関係のスキャン

name: Security

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 0 * * 1'  # 週1回

jobs:
  dependency-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run npm audit
        run: npm audit --audit-level=high

      - name: Run Snyk scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

シークレットの安全な取り扱い

steps:
  # 悪い例: シークレットをログに出力してしまう
  - name: Bad example
    run: echo ${{ secrets.API_KEY }}  # ❌ ログに表示される可能性

  # 良い例: 環境変数として渡す
  - name: Good example
    env:
      API_KEY: ${{ secrets.API_KEY }}
    run: ./deploy.sh  # スクリプト内で$API_KEYを使用

最小権限の原則

# ワークフローレベルで最小限の権限を設定
permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest
    # ジョブレベルで上書きも可能
    permissions:
      contents: read
    steps:
      # ...

デバッグとトラブルシューティング

デバッグログの有効化

# リポジトリのSecretsに設定
# ACTIONS_STEP_DEBUG: true
# ACTIONS_RUNNER_DEBUG: true

steps:
  - name: Debug info
    run: |
      echo "GitHub Context:"
      echo '${{ toJson(github) }}'
      echo "Runner Context:"
      echo '${{ toJson(runner) }}'

手動でのジョブ再実行

on:
  workflow_dispatch:
    inputs:
      debug_enabled:
        description: 'Run with debug logging'
        required: false
        default: 'false'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Setup tmate session
        if: inputs.debug_enabled == 'true'
        uses: mxschmitt/action-tmate@v3

まとめ

GitHub Actionsを使ったCI/CDパイプラインのポイントをまとめます。

ワークフロー設計のベストプラクティス

  1. ジョブの分離: lint、test、build、deployを分離
  2. キャッシュの活用: npm/pip等のキャッシュで高速化
  3. マトリックスビルド: 複数環境での並列テスト
  4. 環境の分離: staging/productionを明確に分離

セキュリティ

  1. 最小権限の原則: 必要最低限のpermissionsを設定
  2. シークレット管理: Environment Secretsを活用
  3. 依存関係スキャン: 定期的な脆弱性チェック
  4. コードスキャン: CodeQLの導入

高速化のポイント

  1. キャッシュの適切な設定
  2. 並列実行の最大化
  3. 不要なステップの削除
  4. スリムなDockerイメージの使用

GitHub Actionsを活用することで、開発プロセスを大幅に効率化できます。

参考リンク

← 一覧に戻る