1. はじめに
以下の記事に記載されているReact勉強ロードマップの課題2のアプリのビルド・テスト・デプロイのCI/CDの作成を取り組んだ。
1-1. インプット
以下の教材の44.まで行った。
1-2. 知識習得
公式ドキュメントの参照
AIに質問するだけでなく、第1次情報である公式ドキュメントを読み解く力が大事といわれていたので挑戦してみた。
公式ドキュメントの中にクイックスタートがあったので、まずはそれを行い全体像をつかんだ。
クイックスタートを行ったことで大体の流れは確認できた。しかし、今回の学習はスピード感を重視する方が重要であり、AIに聞いてもわからないことや・裏側の仕組み・思想のような部分を確認するときにドキュメントを使うというやり方の方が良いと考え、この後はAIを用いた。
AIに質問
入力
「GHAを用いたCI/CDパイプラインの構築について教えて」
出力で学べた内容
GitHub Actions(GHA)を使ったCI/CDパイプラインとは、コードが変更されたときに自動でテスト・ビルド・デプロイを実行する仕組み
CI/CDとは
CI(Continuous Integration)
開発者がコードをPushしたら自動で
- ソースコード取得
- 依存関係インストール
- テスト実行
- ビルド実行
を行う仕組み。
例えば、
git push origin main
した瞬間に
npm install
npm run test
npm run build
が実行される。
もしテストが失敗したら、
❌ Build Failed
となり、問題を早期発見できる。
CD(Continuous Delivery / Deployment)
CIが成功したらさらに
ビルド
↓
本番環境へデプロイ
まで自動化する。
例えば
mainブランチへマージ
↓
テスト成功
↓
Firebase Hostingへデプロイ
である。
GitHub Actionsの基本構成
GitHubリポジトリに
.github/
└── workflows/
└── ci.yml
を作成する。
例:
name: CI
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run test
- run: npm run build
各ステップの意味
checkout
- uses: actions/checkout@v4
GitHub上のコードを取得
setup-node
- uses: actions/setup-node@v4
Node.js環境を作成
npm ci
- run: npm ci
依存関係をインストール
通常は
npm install
よりも
npm ci
が推奨される。
理由
- package-lock.json通りにインストール
- 高速
- 再現性が高い
テスト実行
Vitestなら
- run: npm run test
package.json
{
"scripts": {
"test": "vitest run"
}
}
ビルド実行
Viteなら
- run: npm run build
実行後
dist/
が生成される。
react/ViteアプリのCIの例
最近作成している学習記録アプリなら、
name: React CI
on:
pull_request:
push:
branches:
- main
jobs:
test-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run test
- run: npm run build
Firebase Hostingへ自動デプロイ
Firebaseにデプロイする場合
name: Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: your-project-id
流れは
git push
↓
GitHub Actions起動
↓
npm ci
↓
npm run test
↓
npm run build
↓
Firebaseへデプロイ
補足
Dockerを使う場合
name: Docker CI
on:
push:
branches:
- main
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Image
run: docker build -t app .
- name: Test Container
run: docker run app npm run test
実務でよくあるパイプライン
PR作成
↓
Lint
↓
Unit Test
↓
Build
↓
Docker Build
↓
E2E Test
↓
mainへマージ
↓
本番デプロイ
より具体的には
Push
↓
ESLint
↓
Vitest
↓
Vite Build
↓
Docker Build
↓
Docker Hub Push
↓
Cloud Run / ECS / Kubernetes Deploy
である。
学習手順
現在の学習状況(React、Vitest、Docker、Firebase)を踏まえると、次の順序が理解しやすい。
- GitHub ActionsでVitestを自動実行
- Buildを追加
- ESLintを追加
- Firebase Hostingへ自動デプロイ
- Dockerイメージのビルド
- Docker HubへのPush
- AWSやKubernetesへのデプロイ
まずは「PRを作ったらVitestが自動実行されるCI」を作るのがおすすめ。Reactアプリ開発では最も効果を実感しやすく、実務でもほぼ必須の構成。
1-3. 実装理解
AIに質問
入力
「今回は、buildとtestとdeployを行うパイプラインを作成する。具体的な手順を教えて」
出力で学べた内容
全体像
mainブランチへPush
↓
GitHub Actions起動
↓
npm ci
↓
npm run build
↓
npm run test
↓
Firebase Hostingへデプロイ
WIFを用いたキーレス認証
GHAからFirebaseへ接続するための認証をできるようにする。
Step1 受け入れ準備と「ロボット社員」の採用
Firebaseの裏側は「Google Cloud」というシステムで動いている。Step1では、GitHub ActionsがGoogle Cloud というオフィスビルに安全に入館し、仕事(デプロイ)ができるように準備する。
1. APIの有効化
必要な部屋の鍵を開ける
-
IAM Service Account Credentials API
役割: 今回作成する「ロボット社員(サービスアカウント)」の身分証を一時的に発行するための機能 -
Security Token Service API
役割: GitHubから「私はGitHubのアクションです」という証明書を持ってきた際に、それをGoogle Cloud内で使える「一時的な入場パス(トークン)」に交換する受付窓口の機能 -
Cloud Resource Manager API
役割: Google Cloudのプロジェクト(ビル全体)の情報を読み取るための基本的な機能
2. デプロイ用サービスアカウントの作成
ロボット社員の採用
1.「IAMと管理」-> 「サービスアカウント」
2. 「+ サービス アカウントを作成」
3. 「サービス アカウント名」を入力
4. ロール(権限)に「Firebase Hosting 管理者」を付与
5. 「完了」
Step2 プールとプロバイダの作成
-
プールの作成(Pool):
GitHub Actions専用の「外部からの専用エントランス(受付ロビー)」をGoogle Cloudのビルに増設する作業
gcloud iam workload-identity-pools create "github-actions-pool" \
--project="YOUR_PROJECT_ID" \
--location="global" \
--display-name="GitHub Actions Pool"
-
プロバイダの作成 (Provider):
そのエントランスに、「GitHubが発行した身分証(OIDCトークン)を持っているなら信用する」というマニュアルを持った受付担当者を配置する作業
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--project="YOUR_PROJECT_ID" \
--location="global" \
--workload-identity-pool="github-actions-pool" \
--display-name="GitHub Actions Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
--attribute-condition="assertion.repository == 'あなたのGitHubアカウント名/リポジトリ名'" \
--issuer-uri="https://token.actions.githubusercontent.com"
💡 プールとプロバイダの関係性(おさらい)
-
プール (github-actions-pool):
外部からのアクセスを管理するための「大きな箱(グループ)」です。最初はただの空箱。 -
プロバイダ (github-provider):
前回のコマンドで作ったものです。プールの箱の中に設置する「専用の受付窓口」。
つまり、まずは「プール」という枠組みを作り、その中に「GitHub Actions専用の受付」や(必要であれば)「別の外部サービス専用の受付」を並べて管理していく、という階層構造になっている。
Step3 アカウントとリポジトリの紐づけ
「どのGitHubリポジトリ」が「どのサービスアカウント」になりすまして良いか(信用できるか)の許可を与える。
gcloud iam service-accounts add-iam-policy-binding "あなたのサービスアカウントのメールアドレス" \
--project="YOUR_PROJECT_ID" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/github-actions-pool/attribute.repository/あなたのGitHubアカウント名/リポジトリ名"
補足
それぞれサービスなどの関係値が分かりずらかったので、図を作成した。
GitHub Secrets登録
先ほどまでのステップでGoogle Cloud 側の受け入れ準備が整ったので、Github ActionsがWIFを利用できるように、GitHubリポジトリに環境設定を登録する。
実のところ、WIFのプロバイダ情報やサービスアカウントのメールアドレスはパスワードのような機密情報ではないので、GitHubの**Repository secrets(シークレット)**ではなく、**Repository variables(変数)**に登録するのが良い。(シークレットに登録しても動作は同じ)
GitHubリポジトリ
Settings
↓
Secrets and variables
↓
Actions
↓
New repository variable
を選択。
名前
WIF_PROVIDER
SERVICE_ACCOUNT
FIREBASE_PROJECT_ID
値
projects/あなたのプロジェクト番号/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider
github-actions-deploy@YOUR_PROJECT_ID.iam.gserviceaccount.com
YOUR_PROJECT_ID
ワークフローの作成
name: Build, Test, and Deploy to Firebase
on:
push:
branches:
- main
# 【重要】OIDCトークンを発行してWIFによるキーレス認証を行うために必須の設定
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test -- --run
- name: Build application
run: npm run build
env:
VITE_SUPABASE_URL: ${{ vars.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
# 1. サービスアカウントのJSONキーではなく、WIFを使ってGoogle Cloudの認証を通過する
- name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ vars.WIF_PROVIDER }}
service_account: ${{ vars.SERVICE_ACCOUNT }}
# 2. 認証済みセッションを利用してFirebaseへデプロイを実行する
- name: Deploy to Firebase Hosting
run: npx firebase-tools deploy --only hosting --project ${{ vars.FIREBASE_PROJECT_ID }}
よくある失敗
テストが終わらない
VitestがWatchモードになっている
NG
run: npm run test
OK
run: npm run test -- --run
または
"test:ci": "vitest run""test:ci": "vitest run"
を作り
run: npm run test:ci
にする。
各部分の意味
workflow名
name: Build, Test, and Deploy to Firebase
GitHub Actions画面に表示される名前。
トリガー
on:
push:
branches:
- main
mainブランチにPushされたら実行。
例えば、
git push origin main
で起動する。
job定義
jobs:
deploy:
deploy はJob名。
任意の名前で構いません。
実行環境
runs-on: ubuntu-latest
GitHubが提供するLinux環境で実行する。
実際には新しい仮想マシンが起動される。
ソース取得
- name: Checkout source code
uses: actions/checkout@v4
GitHub上のコードをRunnerへコピー。
これがないとソースコードが存在しません。
Node.jsセットアップ
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
Node.js 22をインストールする。
cache: npm
は依存関係のキャッシュ。
2回目以降が高速になる。
パッケージインストール
- name: Install dependencies
run: npm ci
package-lock.json を利用して依存関係を復元する。
ローカルで言う
npm ci
と同じ。
テスト実行
- name: Run tests
run: npm run test -- --run
VitestのWatchモードを無効にする。
ビルド
- name: Build application
run: npm run build
Viteなら
vite build
が実行され、
dist/
が生成される。
また、環境変数の受け渡し
env:
VITE_SUPABASE_URL: ${{ vars.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
なぜデプロイ時ではなく「ビルド時」に渡すのか?
ReactやNext.jsなどのフロントエンドアプリは、npm run build を実行するタイミングで、環境変数の文字列を静的なファイル(JavaScriptコード)の中に直接埋め込む。そのため、デプロイ時(Firebaseへアップロードする時)ではなく、 ビルドする瞬間に環境変数が存在していること が不可欠です。
(GitHubにSupabaseの環境変数を登録した)
Google Cloud への認証とデプロイ
- name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ vars.WIF_PROVIDER }}
service_account: ${{ vars.SERVICE_ACCOUNT }}
- name: Deploy to Firebase Hosting
run: npx firebase-tools deploy --only hosting --project ${{ vars.FIREBASE_PROJECT_ID }}
id: auth
id: auth
このステップに
auth
という識別子をつけている。
例えば後続で
${{ steps.auth.outputs.access_token }}
のように参照できる。
uses
uses: google-github-actions/auth@v2
GitHub Marketplaceにある
google-github-actions/auth
というActionを実行している。
役割は
GitHub
↓
Google Cloud認証
である。
with
with:
Actionへ渡す設定値。
workload_identity_provider
workload_identity_provider: ${{ vars.WIF_PROVIDER }}
GitHub Variablesに保存された値を渡す。
これは
GitHubのIDを信頼する設定
を表している。
イメージ
Google Cloud
Workload Identity Pool
└ Provider
└ GitHub Repository
このProviderを指定している。
service_account
service_account: ${{ vars.SERVICE_ACCOUNT }}
認証後、
GitHub Actions
↓
このService Accountとして振る舞う
ことになる。
認証後どうなる?
内部的には
gcloud auth login
のような状態。
以降の
firebase deploy
gcloud run deploy
などが実行可能になる。
Deploy to Firebase Hosting
run: npx firebase-tools deploy --only hosting --project ${{ vars.FIREBASE_PROJECT_ID }}
npx firebase-tools
はfirebaseCLIを実行している。
例えば、
firebase deploy
とほぼ同じ。
--only hosting
firebase deploy --only hosting
Hostingだけにデプロイする。
例えば
Hosting
Functions
Firestore Rules
Storage Rules
がある場合でも
Hostingのみ
更新する。
改善点
name: Build, Test, and Deploy to Firebase
on:
push:
branches:
- main
# 1. 重複実行のキャンセル(Concurrency)
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
# 2. タイムアウトの明示的設定
timeout-minutes: 15
# 3. GitHub Environmentsの指定
environment: production
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test -- --run
- name: Build application
run: npm run build
env: # インデントを整理
VITE_SUPABASE_URL: ${{ vars.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
- name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ vars.WIF_PROVIDER }}
service_account: ${{ vars.SERVICE_ACCOUNT }}
- name: Deploy to Firebase Hosting
run: npx firebase-tools deploy --only hosting --project ${{ vars.FIREBASE_PROJECT_ID }}
1. concurrency による無駄なビルドの防止
デプロイ直後に軽微な修正(タイポなど)を見つけて、連続して main にPushすることがある。
cancel-in-progress: true を設定することで、「古いPushのデプロイ作業」を即座にキャンセルし、最新のPushだけを処理するようになる。これにより、GitHub Actionsの無料枠(利用時間)の節約と、古いバージョンが後から上書きデプロイされてしまう事故(競合)を防げる。
2. timeout-minutes: 15 の設定
デフォルトのGitHub Actionsは、何らかのエラー(無限ループや通信のハングアップなど)で処理が止まった場合、最大6時間(360分) ランナーが動き続けてしまう。
フロントエンドのビルドとデプロイであれば通常数分で終わるため、15分程度で強制終了(タイムアウト)させる設定を入れておくと、不測の事態でも安心。
3. environment: production の追加と保護
GitHub側の「Environments(環境)」機能と紐づけるための記述。
これを入れておくと、GitHubリポジトリの設定画面で「デプロイ前に特定のメンバーの承認(Approve)を必須にする」といった強力なアクセス制御が可能になる。現在登録しているSecretsやVariablesも、今後はこの「production環境専用の変数」として管理することで、より高いセキュリティを担保できる。
2. 振り返り
生成AIに聞きながらの勉強は大丈夫だと思う。質問の中に2026年現在のベストプラクティスという言葉を入れる。そしてまずは動くものを作る。アウトプットする。そこで経験値を積んで、「この書き方・文法結構出てくるな」っていうものを公式ドキュメントで仕様ちゃんとみとこみたいなフィードバック方式学習が良いと思った。

