GitHub Actionsは、GitHubが提供する継続的インテグレーション/継続的デリバリー(CI/CD)プラットフォームです。リポジトリへのプッシュやプルリクエストをトリガーに、テストの実行、ビルド、デプロイを自動化できます。本記事では、Node.jsプロジェクトにおけるGitHub Actionsワークフローの作成方法から、複数バージョンでのマトリックステスト、キャッシュによる高速化、npm publishやDocker buildの自動化まで、実践的なCI/CDパイプラインの構築方法を解説します。

実行環境

項目 バージョン
Node.js 20.x LTS以上
npm 10.x以上
OS Windows/macOS/Linux

前提条件

  • JavaScriptの基礎知識があること
  • Node.jsの基本API(package.jsonnpmコマンド)を理解していること
  • GitHubリポジトリの操作経験があること

GitHub Actionsの基本概念

GitHub Actionsを理解するために、まず基本的な用語と概念を整理します。

ワークフロー、ジョブ、ステップの関係

flowchart TB
    subgraph Workflow["Workflow(.github/workflows/*.yml)"]
        subgraph Job1["Job: build"]
            Step1A["Step: Checkout"]
            Step1B["Step: Setup Node.js"]
            Step1C["Step: Install dependencies"]
            Step1D["Step: Run tests"]
            Step1A --> Step1B --> Step1C --> Step1D
        end
        subgraph Job2["Job: deploy"]
            Step2A["Step: Deploy to production"]
        end
        Job1 --> Job2
    end
用語 説明
Workflow .github/workflowsディレクトリに配置するYAMLファイル。CI/CD全体の定義
Job ワークフロー内で実行される一連のステップ。並列または順次実行が可能
Step ジョブ内の個々のタスク。アクションの実行またはシェルコマンドの実行
Action 再利用可能なワークフローの構成要素。actions/checkoutなどのマーケットプレイス公開アクション
Runner ワークフローを実行するサーバー。GitHubホステッドまたはセルフホステッド

ワークフローファイルの基本構造

.github/workflows/ci.ymlとして配置する基本的なワークフローファイルの構造を示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# ワークフローの名前(GitHub Actionsタブに表示される)
name: Node.js CI

# トリガー条件の定義
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

# ジョブの定義
jobs:
  build:
    # 実行環境の指定
    runs-on: ubuntu-latest

    # ジョブで実行するステップ
    steps:
      - name: Checkout repository
        uses: actions/checkout@v5

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

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

基本的なNode.js CIワークフロー

実用的なNode.js CIワークフローを段階的に構築していきます。

シンプルなテスト実行ワークフロー

最小構成のCIワークフローです。プッシュとプルリクエスト時にテストを自動実行します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# .github/workflows/ci.yml
name: Node.js CI

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

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'

      - name: Install dependencies
        run: npm ci

      - name: Run build
        run: npm run build --if-present

      - name: Run tests
        run: npm test

npm cinpm installと異なり、package-lock.jsonに基づいて厳密に依存関係をインストールします。CI環境ではこちらの使用が推奨されます。

トリガーイベントの詳細設定

ワークフローのトリガー条件は柔軟にカスタマイズできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
on:
  # プッシュ時のトリガー
  push:
    branches:
      - main
      - 'release/**'  # release/v1.0などのブランチ
    paths:
      - 'src/**'       # srcディレクトリ配下の変更時のみ
      - 'package.json'
    paths-ignore:
      - '**.md'        # Markdownファイルの変更は除外
      - 'docs/**'

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

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

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

マトリックス戦略による複数バージョンテスト

Node.jsプロジェクトでは、複数のNode.jsバージョンでテストを実行することが重要です。マトリックス戦略を使用すると、異なるバージョン・OS環境での並列テストが可能になります。

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# .github/workflows/ci.yml
name: Node.js CI

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

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      # 1つのジョブが失敗しても他のジョブを継続
      fail-fast: false
      matrix:
        node-version: ['18.x', '20.x', '22.x']

    steps:
      - uses: actions/checkout@v5

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

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

このワークフローは、Node.js 18.x、20.x、22.xの3つのバージョンで並列にテストを実行します。

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

OSとNode.jsバージョンの組み合わせでテストする場合は、複数のマトリックス軸を定義します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: ['18.x', '20.x']

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

    steps:
      - uses: actions/checkout@v5

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

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

特定の組み合わせを除外・追加

excludeincludeを使用して、マトリックスの組み合わせを調整できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: ['18.x', '20.x', '22.x']
        exclude:
          # Windows + Node.js 18の組み合わせを除外
          - os: windows-latest
            node-version: '18.x'
        include:
          # 特定の組み合わせを追加(実験的なバージョン)
          - os: ubuntu-latest
            node-version: '23.x'
            experimental: true

    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.experimental || false }}

    steps:
      - uses: actions/checkout@v5

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

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

キャッシュによるCI高速化

依存関係のキャッシュは、CIパイプラインの実行時間を大幅に短縮できます。

setup-nodeアクションの組み込みキャッシュ

actions/setup-node@v4には依存関係キャッシュ機能が組み込まれています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - name: Setup Node.js with cache
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          cache: 'npm'  # npm, yarn, pnpmに対応

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

cache: 'npm'を指定するだけで、~/.npmキャッシュディレクトリが自動的にキャッシュされます。

actions/cacheによる詳細なキャッシュ制御

より細かいキャッシュ制御が必要な場合は、actions/cacheを直接使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

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

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

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

      - name: Run tests
        run: npm test

キャッシュの仕組みと効果

flowchart LR
    subgraph FirstRun["初回実行"]
        A1["npm ci 実行"] --> A2["node_modules 生成"]
        A2 --> A3["キャッシュ保存"]
    end
    subgraph SecondRun["2回目以降"]
        B1["キャッシュキー確認"] --> B2{"キャッシュ<br>ヒット?"}
        B2 -->|Yes| B3["キャッシュ復元"]
        B2 -->|No| B4["npm ci 実行"]
        B3 --> B5["npm ci スキップ"]
    end
項目 キャッシュなし キャッシュあり
依存関係インストール時間 30秒〜2分 5〜10秒
ネットワーク通信 毎回ダウンロード キャッシュから復元
課金時間 長い 短い

Lintとテストの統合

実務では、テストだけでなくLintやフォーマットチェックもCIに組み込みます。

包括的なCIワークフロー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# .github/workflows/ci.yml
name: CI

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

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

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

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

      - name: Check formatting
        run: npm run format:check

  test:
    name: Test
    runs-on: ubuntu-latest
    needs: lint  # lintジョブが成功した後に実行

    strategy:
      matrix:
        node-version: ['18.x', '20.x', '22.x']

    steps:
      - uses: actions/checkout@v5

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Upload coverage
        if: matrix.node-version == '20.x'
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage/lcov.info
          fail_ci_if_error: false

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: test  # testジョブが成功した後に実行

    steps:
      - uses: actions/checkout@v5

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

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

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

ジョブ間の依存関係

flowchart LR
    A["lint"] --> B["test"]
    B --> C["build"]

needsキーワードを使用することで、ジョブ間の実行順序を制御できます。needs: lintを指定すると、lintジョブが成功した場合にのみtestジョブが実行されます。

npm publishの自動化

パッケージをnpmレジストリに自動公開するワークフローを構築します。

リリース時の自動公開

GitHubでリリースを作成した際に自動的にnpmに公開するワークフローです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# .github/workflows/publish.yml
name: Publish Package

on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write

    steps:
      - uses: actions/checkout@v5

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          registry-url: 'https://registry.npmjs.org'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

      - name: Publish to npm
        run: npm publish --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

npm認証トークンの設定手順

npmへの公開には認証トークンが必要です。以下の手順で設定します。

  1. npmにログインし、Access Tokensページ(https://www.npmjs.com/settings/~/tokens)にアクセス
  2. 「Generate New Token」をクリックし、「Automation」タイプを選択
  3. 生成されたトークンをコピー
  4. GitHubリポジトリの「Settings」→「Secrets and variables」→「Actions」→「New repository secret」を選択
  5. 名前をNPM_TOKEN、値に先ほどのトークンを設定

GitHub Packagesへの公開

npmレジストリではなくGitHub Packagesに公開する場合は、以下のように設定します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# .github/workflows/publish-gpr.yml
name: Publish to GitHub Packages

on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v5

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          registry-url: 'https://npm.pkg.github.com'
          scope: '@your-username'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Publish to GitHub Packages
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

package.jsonには以下の設定が必要です。

1
2
3
4
5
6
{
  "name": "@your-username/your-package",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  }
}

Docker buildの自動化

Node.jsアプリケーションをDockerイメージとしてビルド・プッシュするワークフローを構築します。

GitHub Container Registryへのプッシュ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# .github/workflows/docker.yml
name: Docker Build and Push

on:
  push:
    branches: [main]
    tags:
      - 'v*'

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

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

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

      - 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 for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Node.js用のDockerfile例

上記ワークフローで使用するDockerfileの例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# ビルドステージ
FROM node:20-alpine AS builder

WORKDIR /app

# 依存関係のインストール
COPY package*.json ./
RUN npm ci --only=production

# ソースコードのコピーとビルド
COPY . .
RUN npm run build

# 実行ステージ
FROM node:20-alpine AS runner

WORKDIR /app

# セキュリティ: non-rootユーザーで実行
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 appuser

# 本番用ファイルのコピー
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

USER appuser

EXPOSE 3000

CMD ["node", "dist/index.js"]

Docker Hubへの公開

Docker Hubにイメージを公開する場合は、以下のように設定します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# .github/workflows/docker-hub.yml
name: Docker Hub Publish

on:
  release:
    types: [published]

jobs:
  push:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: your-dockerhub-username/your-app
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

環境変数とSecretsの管理

CI/CD環境での機密情報と環境設定の管理方法を解説します。

Secretsの設定と使用

GitHubリポジトリのSecretsには、APIキーやトークンなどの機密情報を安全に保存できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Deploy
        run: |
          curl -X POST ${{ secrets.DEPLOY_URL }} \
            -H "Authorization: Bearer ${{ secrets.DEPLOY_TOKEN }}"
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.API_KEY }}

環境(Environments)の活用

GitHub Environmentsを使用すると、本番環境用のSecretsと承認ワークフローを設定できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v5
      - name: Deploy to staging
        run: npm run deploy
        env:
          DEPLOY_URL: ${{ secrets.DEPLOY_URL }}

  deploy-production:
    runs-on: ubuntu-latest
    needs: deploy-staging
    environment:
      name: production
      url: https://your-app.com
    steps:
      - uses: actions/checkout@v5
      - name: Deploy to production
        run: npm run deploy:prod
        env:
          DEPLOY_URL: ${{ secrets.DEPLOY_URL }}

環境変数の優先順位

環境変数は以下の優先順位で解決されます。

優先度 定義場所 スコープ
ステップ内のenv 該当ステップのみ
ジョブ内のenv 該当ジョブ内全ステップ
ワークフロー全体のenv 全ジョブ・全ステップ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
env:
  NODE_ENV: production  # ワークフロー全体

jobs:
  build:
    env:
      CI: true  # ジョブ全体

    steps:
      - name: Test
        run: npm test
        env:
          DEBUG: app:*  # このステップのみ

実践的なワークフロー例

ここまでの内容を統合した、実務で使用できる完全なCI/CDワークフローを示します。

完全なCI/CDパイプライン

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  release:
    types: [published]

env:
  NODE_VERSION: '20.x'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # 静的解析
  lint:
    name: Lint & Format Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

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

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

      - name: Check formatting
        run: npm run format:check

  # セキュリティ監査
  security:
    name: Security Audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

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

      - name: Install dependencies
        run: npm ci

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

  # テスト(マトリックス)
  test:
    name: Test (Node.js ${{ matrix.node-version }})
    runs-on: ubuntu-latest
    needs: [lint, security]

    strategy:
      fail-fast: false
      matrix:
        node-version: ['18.x', '20.x', '22.x']

    steps:
      - uses: actions/checkout@v5

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test -- --coverage

      - name: Upload coverage
        if: matrix.node-version == '20.x' && github.event_name == 'push'
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          fail_ci_if_error: false

  # ビルド
  build:
    name: Build
    runs-on: ubuntu-latest
    needs: test

    steps:
      - uses: actions/checkout@v5

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

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

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

  # Dockerイメージのビルドとプッシュ
  docker:
    name: Docker Build & Push
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v5

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/

      - 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=ref,event=branch
            type=semver,pattern={{version}}
            type=sha,prefix=

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  # npm公開(リリース時のみ)
  publish:
    name: Publish to npm
    runs-on: ubuntu-latest
    needs: build
    if: github.event_name == 'release'
    permissions:
      contents: read
      id-token: write

    steps:
      - uses: actions/checkout@v5

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          registry-url: 'https://registry.npmjs.org'

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/

      - name: Install dependencies
        run: npm ci

      - name: Publish
        run: npm publish --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

パイプラインの実行フロー

flowchart TB
    subgraph Trigger["トリガー"]
        Push["Push"]
        PR["Pull Request"]
        Release["Release"]
    end

    subgraph Parallel["並列実行"]
        Lint["Lint & Format"]
        Security["Security Audit"]
    end

    subgraph Matrix["マトリックステスト"]
        Test18["Test Node 18.x"]
        Test20["Test Node 20.x"]
        Test22["Test Node 22.x"]
    end

    Build["Build"]

    subgraph Deploy["デプロイ(条件付き)"]
        Docker["Docker Push"]
        Publish["npm Publish"]
    end

    Push --> Parallel
    PR --> Parallel
    Release --> Parallel

    Parallel --> Matrix
    Matrix --> Build
    Build --> Docker
    Build --> Publish

トラブルシューティング

GitHub Actionsでよく発生する問題と解決方法を紹介します。

キャッシュが効かない場合

キャッシュキーの不一致が原因であることが多いです。

1
2
3
4
5
6
# 問題: package-lock.jsonのパスが間違っている
- uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    #                                        ^^^^^^ ワイルドカードで確実にマッチ

npm ciが失敗する場合

package-lock.jsonpackage.jsonの不整合が原因です。

1
2
3
4
5
6
# ローカルで以下を実行してコミット
rm -rf node_modules
rm package-lock.json
npm install
git add package-lock.json
git commit -m "chore: regenerate package-lock.json"

権限エラーが発生する場合

permissionsを明示的に指定します。

1
2
3
4
5
6
7
jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write  # npm provenanceに必要

ワークフローのデバッグ

デバッグログを有効にするには、リポジトリのSecretsに以下を設定します。

Secret名
ACTIONS_RUNNER_DEBUG true
ACTIONS_STEP_DEBUG true

まとめ

本記事では、GitHub Actionsを使用したNode.jsプロジェクトのCI/CD構築について解説しました。

項目 ポイント
ワークフロー基本 .github/workflows/にYAMLファイルを配置。トリガー、ジョブ、ステップで構成
マトリックステスト 複数Node.jsバージョン・OSでの並列テストが可能
キャッシュ setup-nodecache: 'npm'またはactions/cacheで依存関係をキャッシュ
npm publish secrets.NPM_TOKENを設定し、registry-urlを指定して公開
Docker build docker/build-push-actionでビルドとプッシュを自動化
Secrets管理 リポジトリSecretsに機密情報を保存。Environmentsで環境ごとに分離

GitHub Actionsを活用することで、コードの品質担保からデプロイまでを完全に自動化できます。小さなワークフローから始め、プロジェクトの成長に合わせて段階的に拡張していくことをおすすめします。

参考リンク