プッシュ通知
新記事をすぐにお知らせ
🎙️ 音声: ずんだもん / 春日部つむぎ(VOICEVOX)
Firebase Hosting と CI/CD パイプライン(GitHub Actions)の組み合わせは、Web アプリケーションの本番デプロイを完全に自動化する最も実用的なアプローチです。初期設定には若干の学習コストがありますが、一度構築してしまえば、開発チーム全体の生産性が劇的に向上し、デプロイに関連する人的ミスをほぼ完全に排除できます。本記事では、実装の全プロセスと実務で直面する課題への対処法を詳しく解説していますので、これを参考にして自動デプロイ環境を構築してください。
昨年の秋、東京の恵比寿でエンジニアの交流会・忘年会に参加してきました。その場で何人かのエンジニアと話す機会があったのですが、多くの人が「デプロイ作業が大変」「毎回手動でやるのは疲れる」といった悩みを抱えていることに気づきました。当時の私も同じ悩みを持っていて、毎週金曜日の夜間に本番環境へのデプロイを手動で実行するのが習慣になっていました。
その時の疲労感と、交流会での会話がきっかけとなり、本格的に自動デプロイパイプラインの構築に取り組むことにしたのです。それ以来、Firebase Hosting と GitHub Actions を使った自動デプロイシステムを実装し、運用してきました。この記事では、その経験の中で得た知見、遭遇した課題、そしてそれらの解決方法をまとめています。
Firebase Hosting は、Google が提供するホスティングサービスで、Web アプリケーションやスタティックサイトを高速かつ安全にデプロイできるプラットフォームです。グローバルな CDN(コンテンツ・デリバリー・ネットワーク)を活用して、ユーザーの場所に関わらず高速なコンテンツ配信を実現します。
Firebase Hosting の大きな特徴は、Firebase の他のサービス(Realtime Database、Cloud Firestore、Cloud Functions など)とシームレスに統合できることです。また、SSL/TLS 証明書が自動的に提供されるため、セキュリティ設定の手間がほぼありません。さらに、デプロイ履歴の管理、バージョン管理、ロールバック機能などが組み込まれており、本番環境の運用に必要な機能が揃っています。
Firebase Hosting を使用するには、まず Firebase プロジェクトを作成する必要があります。Firebase Console(https://console.firebase.google.com)にアクセスし、新しいプロジェクトを作成してください。このプロジェクト ID は後ほど CI/CD パイプラインの設定で必要になります。
プロジェクト作成後、ローカル環境に Firebase CLI をインストールします。Firebase CLI は Node.js の npm を使ってインストール可能です:
npm install -g firebase-tools
インストール後、Firebase CLI にログインします:
firebase login
このコマンドを実行すると、ブラウザが開いて Google アカウントでの認証が求められます。認証が完了すると、ローカル環境から Firebase プロジェクトへのアクセスが可能になります。
プロジェクトディレクトリで Firebase を初期化します:
firebase init hosting
このコマンドで、firebase.json と .firebaserc といった設定ファイルが自動生成されます。firebase.json には、どのディレクトリを本番環境にデプロイするかなどの設定が記述されます。
CI/CD は「Continuous Integration / Continuous Deployment」の略で、継続的インテグレーションと継続的デプロイメントを意味します。これは、コード変更が自動的にテストされ、問題がなければ本番環境に自動的にデプロイされるワークフローです。
Continuous Integration(CI) は、開発者がコードをリポジトリにプッシュするたびに、自動的にビルドとテストが実行される仕組みです。これにより、統合エラーを早期に発見できます。
Continuous Deployment(CD) は、テストに合格したコードが自動的に本番環境にデプロイされるプロセスです。手動のデプロイ作業が不要になるため、デプロイの頻度が増え、バグフィックスや新機能のリリースが迅速になります。
GitHub Actions は、GitHub が提供する自動化ツールで、リポジトリ内で発生するイベント(プッシュ、プルリクエスト、スケジュール実行など)に応じて、自動的にワークフローを実行できます。YAML 形式の設定ファイルでワークフローを定義し、/.github/workflows/ ディレクトリに配置するだけで動作開始します。
GitHub Actions の利点は、GitHub リポジトリと統合されているため、追加のサービスを契約する必要がないこと、また無料プランでも十分な実行時間が提供されることです。さらに、コミュニティが提供する様々なアクション(再利用可能なワークフローコンポーネント)を活用することで、複雑な処理を簡潔に記述できます。
自動デプロイを実装する前に、リポジトリの構造を適切に設計することが重要です。一般的な Web アプリケーションプロジェクトの構造は以下のようになります:
project-root/
├── public/ # 本番環境にデプロイするディレクトリ
│ ├── index.html
│ ├── css/
│ ├── js/
│ └── images/
├── src/ # ソースコード
│ ├── components/
│ ├── pages/
│ └── utils/
├── firebase.json # Firebase 設定ファイル
├── .firebaserc # Firebase プロジェクト設定
├── .github/
│ └── workflows/
│ └── deploy.yml # GitHub Actions ワークフロー
├── package.json
└── .gitignore
public/ ディレクトリが本番環境にデプロイされるディレクトリになります。このディレクトリ内のファイルが、Firebase Hosting の本番環境に配置されます。
firebase.json は Firebase Hosting の動作を制御する重要な設定ファイルです。初期化時に自動生成されますが、プロジェクトの要件に応じてカスタマイズが必要です。以下は典型的な設定例です:
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
],
"headers": [
{
"source": "/js/**",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000"
}
]
}
]
}
}
この設定では:
public フィールドで、デプロイ対象のディレクトリを指定ignore フィールドで、デプロイから除外するファイルやディレクトリを指定rewrites フィールドで、すべてのリクエストを index.html にリダイレクト(SPA の場合に必要)headers フィールドで、キャッシュ戦略を設定.firebaserc ファイルには、使用する Firebase プロジェクトの情報が記述されます:
{
"projects": {
"default": "your-project-id"
}
}
このファイルは firebase init で自動生成されますが、複数のプロジェクト(開発環境、ステージング環境、本番環境など)を管理する場合は、以下のように複数のプロジェクトを定義することもできます:
{
"projects": {
"default": "your-project-id-prod",
"staging": "your-project-id-staging",
"development": "your-project-id-dev"
}
}
GitHub Actions のワークフローは、.github/workflows/ ディレクトリに YAML ファイルとして配置します。deploy.yml という名前で新しいファイルを作成してください。以下は基本的なワークフロー構造です:
name: Deploy to Firebase Hosting
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
- name: Deploy to Firebase
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: your-project-id
name: ワークフローの名前で、GitHub の Actions タブに表示されます。
on: ワークフローが実行されるトリガーを指定します。この例では、main ブランチへのプッシュをトリガーとしています。他のトリガーとしては、プルリクエスト、スケジュール実行、手動実行などがあります。
jobs: ワークフロー内で実行するジョブを定義します。各ジョブは独立した実行環境で実行されます。
runs-on: ジョブを実行する環境を指定します。ubuntu-latest は最新の Ubuntu 環境を使用することを意味します。
steps: ジョブ内で順序立てて実行される処理のリストです。各ステップは名前、使用するアクション、入力パラメータから構成されます。
Firebase へのデプロイには認証が必要です。GitHub Actions から Firebase へアクセスするために、サービスアカウントキーを作成し、GitHub シークレットとして登録する必要があります。
まず、Firebase Console でサービスアカウントキーを生成します:
次に、このキーを GitHub リポジトリのシークレットとして登録します:
FIREBASE_SERVICE_ACCOUNT、Value: ダウンロードした JSON ファイルの内容を貼り付けこれで、ワークフロー内で ${{ secrets.FIREBASE_SERVICE_ACCOUNT }} として認証情報にアクセスできるようになります。
デプロイ前にアプリケーションをビルドする必要があります。package.json に適切なビルドスクリプトが定義されていることを確認してください:
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production",
"test": "jest",
"lint": "eslint src/"
}
}
ワークフロー内の npm run build コマンドで、このスクリプトが実行されます。ビルドプロセスは、ソースコードをコンパイル・トランスパイルし、最適化された本番用ファイルを public/ ディレクトリに出力する役割を担います。
本番環境へのデプロイ前に、自動テストを実行することは極めて重要です。テストに失敗した場合は、デプロイを中止する仕組みを構築しましょう。ワークフローに以下のステップを追加します:
- name: Run tests
run: npm test
- name: Run linter
run: npm run lint
テストやリンターでエラーが検出された場合、ワークフローは自動的に中止され、デプロイは実行されません。これにより、品質の低いコードが本番環境に入り込むことを防ぎます。
本番環境で必要な環境変数がある場合、GitHub シークレットを使用して安全に管理できます:
- name: Deploy to Firebase
uses: FirebaseExtended/action-hosting-deploy@v0
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: your-project-id
環境変数は GitHub の Secrets セクションで登録し、ワークフロー内で ${{ secrets.変数名 }} の形式でアクセスします。
本番環境への直接デプロイは危険です。より安全なアプローチは、段階的にデプロイすることです。以下は推奨されるデプロイメント戦略です:
1. プルリクエスト時のプレビューデプロイ
プルリクエストが作成されたときに、変更内容をプレビュー環境にデプロイします。これにより、本番環境に反映する前に変更内容を確認できます:
on:
pull_request:
branches:
- main
jobs:
preview-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm run build
- name: Deploy preview
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: your-project-id
2. main ブランチへのマージ時の本番デプロイ
プルリクエストがレビューされて承認され、main ブランチにマージされたときに、本番環境へのデプロイが実行されます。この時点で、変更内容は既にプレビュー環境でテスト済みです。
デプロイ後に問題が発生した場合、迅速にロールバックできる仕組みが必要です。Firebase Hosting では、過去のデプロイ履歴が自動的に保存されており、Firebase Console から簡単にロールバックできます。
さらに、自動ロールバック機能をワークフローに統合することもできます。例えば、デプロイ後の健全性チェック(ヘルスチェック)に失敗した場合、自動的に前のバージョンに戻すといったことが可能です。
- name: Health check
run: |
response=$(curl -s -o /dev/null -w "%{http_code}" https://your-domain.com/health)
if [ $response != "200" ]; then
echo "Health check failed"
exit 1
fi
状況
ワークフロー実行時に、以下のエラーが発生しました:
Error: Permission denied (publickey).
fatal: Could not read from remote repository.
このエラーは、GitHub Actions から Firebase へのアクセス認証に失敗していることを示していました。
原因分析
サービスアカウントキーを JSON 形式で GitHub シークレットに登録する際に、JSON の構造が破損していたことが原因でした。特に、改行文字やエスケープ文字の扱いが適切でなかったのです。
解決策
サービスアカウントキーを正しく登録する手順を以下に示します:
さらに確実な方法として、JSON ファイルを Base64 エンコードしてから登録し、デコードして使用する方法もあります:
# ローカルでエンコード
base64 -i service-account-key.json
# GitHub シークレットに登録した後、ワークフロー内でデコード
echo ${{ secrets.FIREBASE_SERVICE_ACCOUNT_BASE64 }} | base64 -d > /tmp/service-account-key.json
状況
最初のワークフロー実装では、デプロイに 15 分以上かかることがありました。これは開発効率を大きく損なっていました。
原因分析
ワークフロー実行のたびに、すべての npm パッケージを一から インストールしていたことが主な原因でした。また、ビルドキャッシュが活用されていませんでした。
解決策
キャッシング機能を活用してデプロイ時間を短縮します:
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install dependencies
run: npm ci # npm install の代わりに npm ci を使用
- name: Cache build
uses: actions/cache@v3
with:
path: public
key: ${{ runner.os }}-build-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-
npm ci(clean install)は npm install より高速で、package-lock.json に記録された正確なバージョンをインストールします。この最適化により、デプロイ時間は 15 分から約 3 分に短縮されました。
状況
本番環境で API キーが設定されておらず、API 呼び出しが失敗するという問題が発生しました。
原因分析
環境変数を GitHub シークレットに登録したものの、ワークフローで正しく参照していませんでした。特に、ビルド時に環境変数を埋め込む必要があるのに、デプロイ時にのみ環境変数を設定していたのです。
解決策
ビルドステップで環境変数を設定します:
- name: Build
env:
REACT_APP_API_KEY: ${{ secrets.API_KEY }}
REACT_APP_DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run build
このように、ビルド実行時に環境変数を設定することで、ビルド時にこれらの値がアプリケーションコードに埋め込まれます。
状況
デプロイに失敗しても、それに気づくのに時間がかかることがありました。ワークフロー実行結果を確認する習慣がなかったためです。
原因分析
ワークフロー実行結果の通知機能を設定していませんでした。デプロイ失敗が自動的に開発チームに通知されていなかったのです。
解決策
失敗時の通知機能を追加します。Slack への通知例:
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "Firebase deployment failed",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "❌ Firebase Hosting deployment failed\nRepository: ${{ github.repository }}\nBranch: ${{ github.ref }}\nCommit: ${{ github.sha }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
この設定により、デプロイが失敗したときに自動的に Slack チャンネルに通知が送信されます。これにより、問題を迅速に認識し、対応できるようになりました。
自動デプロイパイプラインを導入する前後での、デプロイ時間の変化を測定しました。
導入前:
導入後:
結果として、デプロイ時間は約 87% 削減され、デプロイ頻度は約 20 倍に増加しました。これにより、バグフィックスや新機能のリリースが迅速になり、ユーザーへの価値提供が加速しました。
自動デプロイ導入前は、手動デプロイ時に以下のようなヒューマンエラーが発生していました:
自動デプロイ導入後、これらのエラーはほぼ完全に排除されました。ワークフローは常に同じ手順で実行されるため、人的ミスの余地がありません。
デプロイ自動化により、エンジニアがデプロイ作業に費やしていた時間が大幅に削減されました。
導入前:
導入後:
削減された時間は、機能開発やコード品質向上といった、より価値の高い業務に充てられるようになりました。
1. シークレット管理
GitHub シークレットに登録する情報は、以下のベストプラクティスに従って管理してください:
2. アクセス制御
on:
push:
branches:
- main
- develop
デプロイワークフローは、重要なブランチ(main など)へのプッシュのみをトリガーとするよう設定してください。これにより、意図しないデプロイを防ぎます。
1. キャッシング戦略
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
npm パッケージのキャッシングにより、ワークフロー実行時間を大幅に短縮できます。
2. 並列処理
複数のジョブを並列実行することで、全体の実行時間を短縮できます:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm run lint
deploy:
needs: [test, lint]
runs-on: ubuntu-latest
steps:
# デプロイ処理
この例では、test と lint が並列実行され、両方が完了した後に deploy が実行されます。
1. 失敗時の自動通知
デプロイ失敗時に自動的に通知を送信する仕組みを構築してください。これにより、問題を迅速に認識し、対応できます。
2. リトライロジック
ネットワークエラーなど一時的な問題でデプロイが失敗することがあります。リトライロジックを実装することで、堅牢性を向上させます:
- name: Deploy to Firebase
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: your-project-id
continue-on-error: true
- name: Retry on failure
if: failure()
run: sleep 30 && npm run deploy
エラー:「Cannot find module」
原因:npm パッケージがインストールされていない
対処:npm ci を使用し、package-lock.json に基づいて正確なバージョンをインストール
エラー:「Firebase project not found」
原因:.firebaserc のプロジェクト ID が間違っている、または存在しない
対処:Firebase Console で正しいプロジェクト ID を確認し、.firebaserc を修正
エラー:「Permission denied」
原因:Firebase サービスアカウントキーが正しく設定されていない 対処:GitHub シークレットに登録したキーの形式を確認、必要に応じて再登録
ワークフロー実行時に詳細なログを出力することで、問題の原因特定が容易になります:
- name: Debug information
run: |
echo "Node version: $(node --version)"
echo "npm version: $(npm --version)"
echo "Build directory contents:"
ls -la public/
Firebase Hosting と GitHub Actions を組み合わせた自動デプロイパイプラインは、以下の成果をもたらしました:
自動デプロイパイプラインの構築後も、継続的な改善が可能です:
1. 監視とアラート
本番環境の健全性を監視し、問題が発生した場合は自動的にアラートを送信する仕組みを構築
2. パフォーマンス分析
デプロイ後のアプリケーションパフォーマンスを自動的に測定し、デグラデーションを検出
3. セキュリティスキャン
ワークフローにセキュリティスキャンを統合し、脆弱性を本番環境へのデプロイ前に検出
4. カナリアデプロイ
本番環境の一部のユーザーに対してのみ新バージョンをデプロイし、問題がないことを確認した後に全体にロールアウト
自動デプロイパイプラインの構築は、一見複雑に見えるかもしれません。しかし、段階的に実装し、各段階で問題を解決していけば、誰でも達成可能です。本記事で紹介した手順と課題解決策を参考に、あなたのプロジェクトにも自動デプロイを導入してみてください。
デプロイ作業から解放されたエンジニアは、より創造的で価値の高い仕事に集中できるようになります。これは個人の成長だけでなく、チーム全体、そしてユーザーへの価値提供を加速させます。自動デプロイパイプラインは、単なる技術的な改善ではなく、開発文化を変革するツールなのです。
記事数の多いカテゴリから探す