-
Notifications
You must be signed in to change notification settings - Fork 0
GitHub Actions
Complete guide to set up automated deployment to Firebase Hosting using GitHub Actions.
GitHub Actions will automatically deploy your app to Firebase whenever you push to a specific branch (e.g., main or production).
Benefits:
- ✅ Automated deployments
- ✅ No manual build/deploy steps
- ✅ Preview deployments for pull requests
- ✅ Consistent deployment process
- ✅ Deployment history
- ✅ Code quality checks (format, lint)
The complete CI/CD pipeline runs in 4 sequential jobs:
graph TD
A[Push to main branch] --> B{Job 1: Format & Lint}
B -->|Pass| C[Job 2: Create Version Tag]
B -->|Fail| X1[❌ Pipeline Stops]
C --> D[Calculate Version]
D --> E[Update package.json]
E --> F[Create Git Tag]
F --> G[Push Tag to GitHub]
G --> H{Job 3: Build & Deploy}
H --> I[Checkout at Tag]
I --> J[Build Project]
J --> K[Deploy to Firebase with Tag]
K --> L[✅ Live on Firebase]
G --> M{Job 4: Create Release}
L --> M
M --> N[Create Archives]
N --> O[Generate Release Notes]
O --> P[Publish GitHub Release]
P --> Q[✅ Release Published]
sequenceDiagram
participant Dev as Developer
participant GH as GitHub
participant Job1 as Format & Lint
participant Job2 as Version & Tag
participant Job3 as Build & Deploy
participant Job4 as Release
participant FB as Firebase Hosting
Dev->>GH: Push to main
GH->>Job1: Start Job 1
Job1->>Job1: Check formatting (Prettier)
Job1->>Job1: Run linter (ESLint)
alt Format or Lint Fails
Job1->>GH: ❌ Stop pipeline
end
Job1->>Job2: ✅ Checks passed, start Job 2
Job2->>Job2: Analyze commit message
Job2->>Job2: Calculate new version
Job2->>Job2: Update package.json
Job2->>GH: Create & push Git tag
Job2->>Job3: ✅ Tag created, start Job 3
Job3->>Job3: Checkout code at tag
Job3->>Job3: Build project
Job3->>FB: Deploy with version tag
FB->>Job3: ✅ Deployment complete
Job2->>Job4: Start Job 4
Job3->>Job4: Wait for deployment
Job4->>Job4: Build project for archives
Job4->>Job4: Create source & build archives
Job4->>Job4: Generate release notes
Job4->>GH: Publish GitHub Release
GH->>Dev: ✅ Complete pipeline success
Before setting up GitHub Actions:
- ✅ Firebase project created
- ✅ Firebase CLI installed locally
- ✅ Repository pushed to GitHub
- ✅ Firebase initialized in your project
# Login to Firebase
firebase login
# Generate service account key
firebase init hosting:githubThis will:
- Create GitHub Actions workflow files
- Set up GitHub secrets automatically
- Configure Firebase service account
- Go to Firebase Console
- Select your project
- Click Project Settings (gear icon)
- Go to Service Accounts tab
- Click Generate New Private Key
- Download the JSON file
- Important: Keep this file secure, never commit it!
- Go to your GitHub repository
- Click Settings
- In left sidebar, click Secrets and variables → Actions
- Click New repository secret
Secret Name: FIREBASE_SERVICE_ACCOUNT
Secret Value:
- Open the downloaded JSON file
- Copy the entire content
- Paste it as the secret value
Example JSON structure:
{
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "abc123...",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-xxxxx@your-project-id.iam.gserviceaccount.com",
"client_id": "123456789",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-xxxxx%40your-project-id.iam.gserviceaccount.com"
}Secret Name: FIREBASE_PROJECT_ID
Secret Value:
- Find your project ID in Firebase Console (Project Settings)
- Or check your
.firebasercfile - Example:
my-firebase-project-123
Steps:
- Go to GitHub repository → Settings → Secrets and variables → Actions
- Click "New repository secret"
- Name:
FIREBASE_PROJECT_ID - Value: Your project ID
- Click "Add secret"
Add each Firebase config as a separate secret:
| Secret Name | Example Value | Where to Find |
|---|---|---|
VITE_FIREBASE_API_KEY |
AIzaSyXXX... |
Firebase Console → Project Settings → General |
VITE_FIREBASE_AUTH_DOMAIN |
my-app.firebaseapp.com |
Firebase Console → Project Settings → General |
VITE_FIREBASE_PROJECT_ID |
my-app-12345 |
Firebase Console → Project Settings → General |
VITE_FIREBASE_STORAGE_BUCKET |
my-app-12345.appspot.com |
Firebase Console → Project Settings → General |
VITE_FIREBASE_MESSAGING_SENDER_ID |
123456789012 |
Firebase Console → Project Settings → General |
VITE_FIREBASE_APP_ID |
1:123456789012:web:abc123 |
Firebase Console → Project Settings → General |
How to add each secret:
- Click New repository secret
- Enter Name (e.g.,
VITE_FIREBASE_API_KEY) - Enter Secret (the value)
- Click Add secret
In your repository, create: .github/workflows/firebase-deploy.yml
name: Deploy to Firebase Hosting
on:
push:
branches:
- main
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: your-project-idReplace your-project-id with your actual Firebase project ID.
name: Deploy to Firebase
on:
push:
branches:
- main # Production
- staging # Staging
- develop # Development
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
# Deploy to production (main branch)
- name: Deploy to Production
if: github.ref == 'refs/heads/main'
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_PROD }}
channelId: live
projectId: my-app-prod
# Deploy to staging (staging branch)
- name: Deploy to Staging
if: github.ref == 'refs/heads/staging'
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_STAGING }}
channelId: live
projectId: my-app-staging
# Deploy to development (develop branch)
- name: Deploy to Development
if: github.ref == 'refs/heads/develop'
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_DEV }}
channelId: live
projectId: my-app-devname: Deploy Preview
on:
pull_request:
branches:
- main
jobs:
build_and_preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
- name: Deploy to Preview Channel
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: your-project-id
expires: 7dThis creates a temporary preview URL for each PR that expires after 7 days.
name: Build, Test, and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
# Add tests if you have them
# - name: Run tests
# run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
- 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-idThis project uses a single unified workflow that handles everything in sequence: format → lint → version → tag → deploy → release. The pipeline ensures code quality before deployment and includes the version tag in the Firebase deployment.
File: .github/workflows/ci-cd.yml
name: CI/CD Pipeline - Format, Lint, Tag, Release, Deploy
on:
push:
branches:
- main
jobs:
# Job 1: Format and Lint Checks
format-and-lint:
name: Format and Lint Checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run format:check
- run: npm run lint
# Job 2: Create Version and Tag
create-version-and-tag:
needs: format-and-lint
runs-on: ubuntu-latest
outputs:
new_version: ${{ steps.new_version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# ... version calculation logic ...
- name: Create and push Git tag
run: |
git tag -a ${{ steps.new_version.outputs.version }} -m "Release"
git push origin ${{ steps.new_version.outputs.version }}
# Job 3: Build and Deploy to Firebase
build-and-deploy:
needs: create-version-and-tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.create-version-and-tag.outputs.new_version }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: ${{ secrets.FIREBASE_PROJECT_ID }}
# Job 4: Create GitHub Release
create-github-release:
needs: [create-version-and-tag, build-and-deploy]
runs-on: ubuntu-latest
steps:
# ... create release archives and publish ...Pipeline Features:
- Job 1 - Format & Lint: Validates code quality
- Job 2 - Version & Tag: Creates semantic version tag
- Job 3 - Build & Deploy: Deploys to Firebase with version tag
- Job 4 - Release: Publishes GitHub Release with archives
Benefits:
- ✅ Sequential execution ensures proper order
- ✅ Code quality verified before versioning
- ✅ Version tag created before deployment
- ✅ Firebase deployment includes version tag
- ✅ GitHub Release created with build artifacts
- ✅ Automatic semantic versioning from commit messages
- ✅ Complete CI/CD pipeline in one workflow
Required GitHub Secrets:
-
FIREBASE_SERVICE_ACCOUNT- Service account JSON -
FIREBASE_PROJECT_ID- Your Firebase project ID
Commit Message Convention:
-
breaking:ormajor:→ Major version bump (v1.0.0 → v2.0.0) -
feat:orminor:→ Minor version bump (v1.0.0 → v1.1.0) - All others → Patch version bump (v1.0.0 → v1.0.1)
- Go to your GitHub repository
- Click Actions tab
- You should see your workflow running
- Click on the workflow to see detailed logs
The workflow will:
- ✅ Checkout code
- ✅ Install Node.js
- ✅ Install dependencies
- ✅ Build project with environment variables
- ✅ Deploy to Firebase Hosting
After successful deployment, check:
- Workflow logs for deployment URL
- Firebase Console → Hosting
- Your live site at
https://your-project-id.web.app
Solution: Make sure all VITE_FIREBASE_* secrets are added to GitHub Secrets.
Verify secrets:
- Go to Repository → Settings → Secrets and variables → Actions
- Check all required secrets are listed
- Re-add any missing secrets
Solution: Check Firebase Service Account permissions.
# Regenerate service account
firebase init hosting:githubSolution: Check workflow file location and branch name.
- File must be at
.github/workflows/firebase-deploy.yml - Branch name in workflow must match your branch (e.g.,
mainnotmaster)
Solution: Update repository settings.
- Go to Settings → Actions → General
- Under "Workflow permissions"
- Select "Read and write permissions"
- Save
# Check .gitignore includes:
.env
.env.local
firebase-service-account.json- Regenerate service account keys every 90 days
- Update GitHub secrets when rotated
- Different service accounts for prod/staging/dev
- Separate Firebase projects for each environment
- Only add secrets that are necessary
- Use organization secrets for multiple repos
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment to Firebase completed!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}GitHub automatically sends email notifications for:
- Failed workflows
- Successful deployments (if enabled in settings)
name: Manual Deploy
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
default: 'staging'
type: choice
options:
- production
- staging
- development
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# ... build steps ...
- name: Deploy to ${{ inputs.environment }}
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: my-app-${{ inputs.environment }}How to use:
- Go to Actions tab
- Select workflow
- Click "Run workflow"
- Choose environment
- Click "Run workflow" button
Free tier:
- 2,000 minutes/month for public repos
- 500 minutes/month for private repos
Optimization tips:
- Use
npm ciinstead ofnpm install(faster) - Cache dependencies
- Only deploy on specific branches
- Use conditional deployments
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm' # Enables caching
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-name: CI/CD Pipeline
on:
push:
branches: [main, staging]
pull_request:
branches: [main]
jobs:
lint-and-test:
name: Lint and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Check build
run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
deploy-preview:
name: Deploy Preview
needs: lint-and-test
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- run: npm ci
- run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: your-project-id
expires: 7d
deploy-production:
name: Deploy to Production
needs: lint-and-test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- run: npm ci
- run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: your-project-idNext: Best Practices →
Firebase React Template | Made with ❤️ | GitHub | Report Issues
Getting Started
Configuration
Advanced Topics
Deployment
- React 19
- Vite
- Firebase 12
- Redux Toolkit
- React Router