Introduction: Automatic Deployment with Firebase and GitHub Actions
Manual deployment is the enemy of productivity and reliability. Every time you run
firebase deploy from your terminal, you introduce the risk of human error:
forgetting to build, deploying from the wrong branch, or skipping tests. In this article
we will build a completely automated deployment system using Firebase Hosting
and GitHub Actions.
Firebase Hosting is a hosting platform for static and dynamic web applications that offers a global CDN, automatic SSL certificates, preview channels for Pull Requests, and instant rollback. Combined with GitHub Actions, it creates a workflow where every push to main is automatically deployed to production, and every Pull Request generates a preview accessible via URL.
What You Will Learn in This Article
- How to set up Firebase CLI and initialize a project
- The structure of
firebase.jsonand its options - How to create automatic preview channels for Pull Requests
- The complete production deployment workflow
- Rollback strategies for handling production errors
- How to configure a custom domain
- Caching headers and redirect rules
- Firebase Hosting compared to alternatives
1. Firebase CLI Setup
The first step is to install and configure the Firebase CLI. This CLI is the main tool for interacting with Firebase from the terminal and CI/CD pipelines.
# Install Firebase CLI globally
npm install -g firebase-tools
# Verify the installation
firebase --version
# Login (opens browser for authentication)
firebase login
# For CI/CD environments, use a token
firebase login:ci
# This generates a token to save as a GitHub secret
# Initialize Firebase in the project
firebase init hosting
During initialization, Firebase will ask some configuration questions. Here are the recommended answers for an Angular project:
- Public directory:
dist/my-app/browser(the Angular build output folder) - Configure as single-page app: Yes (rewrites all URLs to index.html)
- Set up automatic builds with GitHub: Yes (automatically configures workflows)
- Overwrite index.html: No (we use our own)
2. firebase.json Configuration
The firebase.json file is the heart of Firebase Hosting configuration. Here is
a complete and optimized configuration for an Angular project:
{
"hosting": {
"public": "dist/my-app/browser",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
],
"headers": [
{
"source": "**/*.@(js|css)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "**/*.@(jpg|jpeg|gif|png|svg|webp|ico)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=86400"
}
]
},
{
"source": "**/*.@(woff|woff2|ttf|eot)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "**",
"headers": [
{
"key": "X-Frame-Options",
"value": "SAMEORIGIN"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
},
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
}
]
}
]
}
}
Key Sections of firebase.json
| Section | Purpose | Detail |
|---|---|---|
public |
Directory of files to serve | Must match the Angular build output |
rewrites |
SPA routing | All requests are redirected to index.html |
headers |
Caching and security | Aggressive caching for hashed assets, global security headers |
ignore |
Files to exclude | Configuration files and hidden directories |
Redirect Rules
Firebase Hosting supports HTTP redirects for handling legacy URLs or forcing HTTPS:
{
"hosting": {
"redirects": [
{
"source": "/old-blog/:slug",
"destination": "/blog/:slug",
"type": 301
},
{
"source": "/api/**",
"destination": "https://api.example.com/v1/:splat",
"type": 302
},
{
"source": "/docs",
"destination": "https://docs.example.com",
"type": 301
}
]
}
}
3. Preview Channels for Pull Requests
Preview channels are one of the most powerful features of Firebase Hosting. Every Pull Request automatically gets a unique URL where changes can be viewed before merging. Here is the complete workflow:
# .github/workflows/firebase-preview.yml
name: Firebase Preview Deploy
on:
pull_request:
branches: [main]
jobs:
build-and-preview:
name: Build and Deploy Preview
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npx ng test --watch=false --browsers=ChromeHeadless
- name: Build application
run: npx ng build --configuration=production
- name: Deploy to Firebase Preview Channel
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: my-angular-project
expires: 7d
After deployment, the action automatically adds a comment to the Pull Request with the preview
URL. The expires: 7d parameter configures automatic expiration of the preview
channel after 7 days.
How to Obtain the Firebase Service Account
- Go to the Firebase Console
- Select project > Project settings > Service accounts
- Click "Generate new private key"
- Download the JSON file
- Copy the content and add it as a GitHub secret named
FIREBASE_SERVICE_ACCOUNT
4. Production Deployment Workflow
The production workflow runs only on pushes to the main branch and includes all
quality checks before deployment:
# .github/workflows/firebase-production.yml
name: Firebase Production Deploy
on:
push:
branches: [main]
concurrency:
group: production-deploy
cancel-in-progress: false
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npx ng lint
- run: npx ng test --watch=false --browsers=ChromeHeadless
build:
name: Build Application
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npx ng build --configuration=production
- uses: actions/upload-artifact@v4
with:
name: production-build
path: dist/
retention-days: 1
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: production-build
path: dist/
- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: my-angular-project
Concurrency for Production Deploys
Note the cancel-in-progress: false setting for the production deploy. Unlike CI
where we can cancel previous builds, for production deployment it is better to complete the
ongoing deploy before starting a new one, to avoid inconsistent states.
5. Rollback Strategy
Despite tests and reviews, sometimes a production deploy introduces issues. Firebase Hosting offers several rollback strategies:
Rollback from the Firebase Console
The Firebase Console maintains deployment history. To rollback to a previous version, go to Hosting > Release history and click "Rollback" next to the desired version. This process is instant because Firebase keeps all files from previous releases on the CDN.
Rollback via CLI
# List recent releases
firebase hosting:releases:list --limit 10
# Rollback to a specific release
firebase hosting:rollback --release RELEASE_ID
# Alternative: clone a previous release
firebase hosting:clone SOURCE_SITE:SOURCE_CHANNEL TARGET_SITE:live
Automated Rollback via GitHub Actions
# .github/workflows/rollback.yml
name: Emergency Rollback
on:
workflow_dispatch:
inputs:
commit-sha:
description: 'Commit SHA to rollback to'
required: true
type: string
reason:
description: 'Reason for rollback'
required: true
type: string
jobs:
rollback:
name: Rollback to Previous Version
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout specific commit
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit-sha }}
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npx ng build --configuration=production
- name: Deploy rollback version
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: my-angular-project
- name: Notify team
run: |
echo "Rollback completed"
echo "Commit: ${{ inputs.commit-sha }}"
echo "Reason: ${{ inputs.reason }}"
6. Custom Domain Configuration
Firebase Hosting supports custom domains with a free SSL certificate. The configuration process requires domain ownership verification through DNS records.
Configuration Steps
- Go to Firebase Console > Hosting > "Add custom domain"
- Enter your domain (e.g.
example.com) - Firebase will provide the DNS records to configure at your registrar
- Add the TXT records for verification and A records for routing
- Wait for DNS propagation (up to 48 hours, typically 1-4 hours)
- Firebase will automatically issue an SSL certificate
DNS records to configure:
# Domain ownership verification
TXT @ firebase-hosting-verification=xxxxxxxxxxxxx
# Routing to Firebase CDN
A @ 151.101.1.195
A @ 151.101.65.195
# www subdomain (optional)
CNAME www my-project.web.app
7. Optimized Caching Headers
Proper caching configuration is fundamental for performance. Angular generates files with hashes
in their names (e.g. main.abc123.js), which can be cached for an entire year.
The index.html file however must never be cached, because it contains references
to the hashed files:
{
"hosting": {
"headers": [
{
"source": "/index.html",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache, no-store, must-revalidate"
}
]
},
{
"source": "ngsw.json",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache"
}
]
},
{
"source": "**/*.@(js|css)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "**/*.@(jpg|jpeg|png|gif|svg|webp|avif|ico)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=604800"
}
]
},
{
"source": "**/*.@(woff|woff2)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}
}
8. Comparison with Alternatives
Firebase Hosting is not the only option for deploying Angular applications. Here is a comparison with the most popular alternatives:
Firebase Hosting vs Alternatives
| Feature | Firebase | Vercel | Netlify | AWS S3 + CloudFront |
|---|---|---|---|---|
| Setup | Simple | Very simple | Very simple | Complex |
| Free SSL | Yes | Yes | Yes | Yes (ACM) |
| Global CDN | Yes | Yes | Yes | Yes |
| Preview deploy | Yes | Yes (automatic) | Yes (automatic) | Manual |
| Rollback | Instant | Instant | Instant | Manual |
| Free plan | 10 GB storage, 360 MB/day transfer | 100 GB bandwidth | 100 GB bandwidth | Pay-as-you-go |
| SSR Support | Yes (Cloud Functions) | Yes (native) | Yes (Functions) | Yes (Lambda@Edge) |
9. Testing the Deploy Locally
Before configuring automatic deployment, it is useful to test the Firebase configuration locally:
# Build the application
npm run build
# Test Firebase Hosting locally
firebase emulators:start --only hosting
# The site will be available at http://localhost:5000
# This simulates Firebase Hosting behavior exactly:
# - SPA rewrites
# - Custom headers
# - Redirect rules
# - Clean URLs
# Alternative: serve the build directory directly
firebase serve --only hosting
Pre-Deploy Checklist
- The production build generates without errors
- All tests pass
- The
firebase.jsonfile points to the correct directory - Caching headers are configured correctly
- Redirects for legacy URLs are in place
- The local emulator shows the site correctly
- The Firebase service account is configured as a GitHub secret
- Branch protection rules are active
Conclusion
In this article we built a completely automated deployment system with Firebase Hosting and GitHub Actions. We configured preview channels for Pull Requests, production workflows with quality checks, rollback strategies for handling emergencies, and optimized caching and security configuration.
The main advantage of this approach is confidence: once configured, every change that passes review and tests is automatically deployed, without manual intervention and without the possibility of errors. And if something goes wrong, rollback is just a click away.
In the next article we will tackle a crucial security topic: secret management in CI/CD pipelines, exploring how to protect API keys, service tokens, and credentials during the build and deployment process.
Related deep dive: 2 - Docker per Sviluppatori Frontend: Containerizzare Angular.
Related deep dive: 4 - Gestione dei Segreti nelle Pipeline CI/CD.
Frequently asked questions
What is the main advantage of using an automated deployment system with Firebase Hosting and GitHub Actions?
The main advantage is confidence, as every change that passes review and tests is automatically deployed without manual intervention or possibility of errors.
How can you rollback to a previous version if a production deploy introduces issues?
You can rollback from the Firebase Console by going to Hosting Release history and clicking "Rollback" next to the desired version, which is an instant process because Firebase keeps all files from previous releases on the CDN.
What is the purpose of preview channels for Pull Requests in Firebase Hosting?
Preview channels automatically generate a unique URL where changes can be viewed before merging, allowing you to test and review changes without affecting production.
Why should you complete an ongoing deploy before starting a new one during production deployment?
It is better to complete the ongoing deploy first to avoid inconsistent states, as opposed to CI pipelines where previous builds can be canceled.
Commenti
Caricamento commenti...
Sign in to comment