GitHub Actions は GitHub に組み込まれた CI/CD プラットフォームで、リポジトリの中に直接自動化フローを定義できます。Jenkins や CircleCI などの外部ツールは不要です。本記事では本サイト(Nuxt 4 + pnpm プロジェクト)を例に、概念から実践まで、プッシュ後に自動ビルド・デプロイを実現する手順を解説します。
基本概念
設定を書く前に、まず用語を整理しておきましょう。
| 用語 | 説明 |
|---|---|
| Workflow | 自動化フロー全体。.github/workflows/ 以下に YAML ファイルとして保存される |
| Event | Workflow を起動するトリガー。push・pull_request・schedule・手動実行など |
| Job | Workflow 内の実行単位。各 Job は独立した VM で動き、デフォルトは並列実行 |
| Step | Job 内の単一操作。順番に実行され、シェルスクリプトまたは Action を指定できる |
| Action | GitHub Marketplace や自作の再利用可能モジュール。uses で参照する |
| Runner | Job を実行する VM。GitHub が提供する hosted runner と自前の self-hosted がある |
| Secret | 暗号化された環境変数。リポジトリの Settings で設定し、${{ secrets.MY_KEY }} で参照する |
Workflow ファイルの構造
すべての Workflow ファイルはリポジトリルートの .github/workflows/ 以下に置きます。ファイル名は自由で、拡張子は .yml でも .yaml でも構いません。
最もシンプルな例:
name: Deploy # Actions ページに表示される名前
on: # トリガー条件
push:
branches:
- main
jobs: # 1 つ以上の Job
build: # Job ID — 自由に命名
runs-on: ubuntu-latest # 使用する Runner
steps: # この Job のステップ
- name: コードをチェックアウト
uses: actions/checkout@v4
- name: Node バージョンを表示
run: node -v
トリガーイベント詳解
on:
# 指定ブランチへのプッシュ時
push:
branches:
- main
- 'release/**' # glob パターン対応
paths:
- 'app/**' # このパスが変更されたときのみ(省略可)
# PR の操作時
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
# スケジュール実行(cron 構文、UTC タイムゾーン)
schedule:
- cron: '0 2 * * *' # 毎日 UTC 02:00
# 手動実行(Actions ページにボタンが表示される)
workflow_dispatch:
inputs:
environment:
description: 'デプロイ環境'
required: true
default: 'production'
type: choice
options:
- production
- staging
実践:本サイトの CI/CD 構成
本サイトは Nuxt 4 プロジェクトで、pnpm で依存関係を管理し、PM2 でサーバー上で稼働しています。目標は以下の 2 つです。
- CI(コードチェック):PR またはプッシュのたびに ESLint を自動実行
- CD(自動デプロイ):
mainブランチへのプッシュ時に Runner 上でビルドし、成果物を圧縮してSCP でサーバーに転送、SSH で展開して PM2 を再起動
ステップ 1:Secrets の準備
リポジトリの Settings → Secrets and variables → Actions に以下の変数を追加します。
| Secret 名 | 内容 |
|---|---|
SSH_HOST | サーバーの IP またはホスト名(例:123.45.67.89) |
SSH_PORT | SSH ポート。デフォルトは 22 |
SSH_USER | ログインユーザー名(例:root または deploy) |
SSH_KEY | 秘密鍵の内容(cat ~/.ssh/id_ed25519 の出力) |
SSH_PATH | サーバー上のデプロイディレクトリ(例:/www/wwwroot/fonnpo-web) |
秘密鍵の設定:
~/.ssh/id_ed25519の内容(-----BEGIN...と-----END...行を含む全文)をSSH_KEYに貼り付けます。対応する公開鍵 (~/.ssh/id_ed25519.pub) はサーバーの~/.ssh/authorized_keysに追記してください。
ステップ 2:CI Workflow(コードチェック)
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
name: ESLint チェック
runs-on: ubuntu-latest
steps:
- name: コードをチェックアウト
uses: actions/checkout@v4
- name: pnpm をセットアップ
uses: pnpm/action-setup@v4
with:
version: 10
- name: Node.js をセットアップ
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm # pnpm store をキャッシュしてインストールを高速化
- name: 依存関係のインストール
run: pnpm install --frozen-lockfile
- name: ESLint を実行
run: pnpm eslint .
--frozen-lockfile により、CI 環境は常に pnpm-lock.yaml の通りにインストールされ、バージョンが勝手に上がることがありません。
ステップ 3:CD Workflow(自動デプロイ)
本サイトはRunner 上でビルドして成果物をサーバーに転送する方針を採用しています。サーバー上で git pull + build する方式と比べ、サーバーに Node.js ビルド環境が不要、ビルド失敗が本番に影響しない、サーバー負荷が低いといったメリットがあります。
name: Deploy to Self-hosted Server
on:
push:
branches: [main]
workflow_dispatch: # Actions ページからの手動実行を有効化
jobs:
deploy:
runs-on: ubuntu-latest
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true # Actions ランタイムを Node 24 に強制
NODE_OPTIONS: --max-old-space-size=4096 # 大規模ビルドの OOM を防止
steps:
- name: コードをチェックアウト
uses: actions/checkout@v4
- name: pnpm をセットアップ
uses: pnpm/action-setup@v4
- name: Node.js をセットアップ
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: 依存関係のインストール
run: pnpm install --frozen-lockfile
- name: ビルド
run: pnpm build
- name: 成果物を圧縮
run: |
tar -czf release.tgz .output ecosystem.config.cjs package.json pnpm-lock.yaml
- name: サーバーにアップロード(SCP)
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
source: release.tgz
target: ${{ secrets.SSH_PATH }}
- name: サーバーで展開・再起動
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
set -e
cd ${{ secrets.SSH_PATH }}
tar -xzf release.tgz
rm -f release.tgz
corepack enable
pnpm install --prod --frozen-lockfile
pm2 startOrReload ecosystem.config.cjs --update-env
pm2 save
ポイント:
envブロック:FORCE_JAVASCRIPT_ACTIONS_TO_NODE24で Actions 内部ランタイムを Node 24 に統一。NODE_OPTIONSでヒープ上限を拡大し、Nuxt の大規模ビルドでの OOM を防止tar圧縮:.output(Nuxt ビルド成果物)、ecosystem.config.cjs、package.json、pnpm-lock.yamlのみを含める。ソースコードは転送しないappleboy/scp-action:SCP プロトコルでアーカイブをサーバーのSSH_PATHに転送pnpm install --prod:本番依存関係のみインストール。ビルドツールは不要でサイズも抑えられるpm2 startOrReload:pm2 reloadより堅牢。プロセスが存在しなければ起動、存在すればダウンタイムなしでリロード。pm2 saveでプロセス一覧を永続化し、サーバー再起動後も自動復元できる
ステップ 4:1 ファイルにまとめる(CI → CD)
CI が通った場合のみデプロイしたい場合は、needs で Job を直列化します。
name: CI / CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
name: ESLint チェック
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm eslint .
deploy:
name: 本番サーバーへデプロイ
runs-on: ubuntu-latest
needs: lint # ← lint が成功するまで待機
if: github.ref == 'refs/heads/main' # ← PR はデプロイしない。main へのプッシュのみ
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
NODE_OPTIONS: --max-old-space-size=4096
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: 成果物を圧縮
run: tar -czf release.tgz .output ecosystem.config.cjs package.json pnpm-lock.yaml
- name: サーバーにアップロード(SCP)
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
source: release.tgz
target: ${{ secrets.SSH_PATH }}
- name: サーバーで展開・再起動
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
set -e
cd ${{ secrets.SSH_PATH }}
tar -xzf release.tgz && rm -f release.tgz
corepack enable
pnpm install --prod --frozen-lockfile
pm2 startOrReload ecosystem.config.cjs --update-env
pm2 save
PR では lint のみ実行されます。main へのマージ後は lint → deploy の順に実行され、両方が成功して初めて完了となります。
Tips
依存関係のキャッシュでビルドを高速化
actions/setup-node の cache: 'pnpm' を使うと pnpm store が自動キャッシュされ、pnpm install が大幅に短縮されます。
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm # pnpm-lock.yaml を元にキャッシュキーを自動生成
環境変数と Secrets の違い
env:
NODE_ENV: production # 平文 — ログに表示される
steps:
- run: echo ${{ secrets.TOKEN }} # Secret — ログでは *** にマスクされる
機密情報(パスワード、トークン、秘密鍵)は必ず Secrets を使ってください。
手動デプロイのトリガー
コードをプッシュせずに再デプロイしたい場合(サーバー再起動後など)は workflow_dispatch を追加します。
on:
workflow_dispatch: # Actions ページに "Run workflow" ボタンが表示される
push:
branches: [main]
Job 間のデータ共有
Job 間は分離されています。outputs を使ってデータを受け渡します。
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get-version.outputs.version }}
steps:
- id: get-version
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "バージョン ${{ needs.build.outputs.version }} をデプロイ"
特定パスの変更時のみトリガー
フロントエンドとバックエンドが同一リポジトリにある場合、関連ファイルが変更されたときのみフローを起動できます。
on:
push:
paths:
- 'app/**'
- nuxt.config.ts
- package.json
- pnpm-lock.yaml
branches: [main]
実行結果の確認
プッシュ後は GitHub リポジトリの Actions タブですべての実行履歴を確認できます。クリックすると各ステップのログが展開され、失敗時は赤くハイライトされるので原因を素早く特定できます。
設定後の完全な Workflow のイメージ:
Actions
└── Deploy to Self-hosted Server
├── 🟢 lint (20s)
│ ├── コードをチェックアウト
│ ├── pnpm / Node.js をセットアップ
│ ├── pnpm install
│ └── ESLint チェック
└── 🟢 deploy (3min)
├── コードをチェックアウト
├── pnpm / Node.js をセットアップ
├── pnpm install
├── pnpm build
├── tar で圧縮
├── SCP でサーバーにアップロード
└── SSH で展開・PM2 再起動
├── tar -xzf release.tgz
├── pnpm install --prod
├── pm2 startOrReload
└── pm2 save
一度設定してしまえば、あとは git push だけで全自動。手動 SSH でのデプロイ作業が不要になります。
Fonnpo