I recently set up a GitHub action to deploy Firebase after pull request merge. It's a tremendous time-saver. Previously, I was deploying from my dev machine, doing some toil to switch between a development environment (emulators) and the production environment.
Project setup
I use environment variables to control the various Firebase variables (project/app ID, API key, etc). Note that the Firebase API key is not a secret key. I put these in .envrc on my local machine but the action needs a bit more help setting up the environment.
I have a script that uses jq to create a JSON file from a template. For example, write-config.sh
#!/bin/bash # Write the overall firebase config: jq -n \ --arg FIREBASE_PROJECT_ID "$FIREBASE_PROJECT_ID" \ -f .firebaserc.jq \ > .firebaserc # Write the json file loaded by the kotlin-angular build: jq -n \ --arg FIREBASE_PROJECT_ID "$FIREBASE_PROJECT_ID" \ --arg FIREBASE_APP_ID "$FIREBASE_APP_ID" \ --arg FIREBASE_STORAGE_BUCKET "$FIREBASE_STORAGE_BUCKET" \ --arg FIREBASE_API_KEY "$FIREBASE_API_KEY" \ --arg FIREBASE_AUTH_DOMAIN "$FIREBASE_AUTH_DOMAIN" \ --arg FIREBASE_MESSAGING_SENDER_ID "$FIREBASE_MESSAGING_SENDER_ID" \ --arg FIREBASE_USE_EMULATORS "$FIREBASE_USE_EMULATORS" \ -f webApp/src/jsMain/resources/firebase-config.json.jq \ > webApp/src/jsMain/resources/firebase-config.json The template files are quite simple, here's one for firebase.json (used by the CLI):
{ "projects": { "default": "\($FIREBASE_PROJECT_ID)" } } You also need your Firebase environment configured in the client. This particular project builds Angular via gradle (it's a long story, see also Kotlin in the Browser). But I used the same json format that Angular Fire recommends. Here's the firebase-config.json.jq template:
{ "projectId": "\($FIREBASE_PROJECT_ID)", "appId": "\($FIREBASE_APP_ID)", "apiKey": "\($FIREBASE_API_KEY)", "authDomain": "\($FIREBASE_AUTH_DOMAIN)", "storageBucket": "\($FIREBASE_STORAGE_BUCKET)", "messagingSenderId": "\($FIREBASE_MESSAGING_SENDER_ID)", "useEmulators": "\($FIREBASE_USE_EMULATORS)" } Repository setup
Set up a target environment (eg "Production") and populate it with values from the Firebase console:
FIREBASE_PROJECT_IDFIREBASE_APP_IDFIREBASE_STORAGE_BUCKETFIREBASE_API_KEYFIREBASE_AUTH_DOMAINFIREBASE_MESSAGING_SENDER_ID
Also, create a secret named FIREBASE_SERVICE_ACCOUNT_BASE64 containing a newly exported json service account key (see below).
GitHub action
The action is a straightforward series of commands,
- check out the repo
- Generate config (as above)
- Install Firebase CLI
- Build the app with gradle
- Deploy web app to Hosting
- Deploy Firestore rules
- Clean up
Services deployed
The following Firebase services are deployed:
- Hosting
- Provides the main web app
- Firestore (rules)
- Defines the database security rules
Service account & permissions required
Create a new service account in the GCP IAM console panel. Call it something like "GitHub deploy", and only use it for GitHub action deploys.
The permissions are a bit trickier. The easy way out is to make the service account an overall admin, but consider following the principle of least privilege. Limit the impact of a malicious or mistaken actor.
Through trial and error I think this is it:
- Firebase Hosting Admin
- Needed to deploy to Hosting
- Firebase Rules Admin
- Needed to deploy Rules (for Firestore)
- Service Account User
- Needed to act as the service account
- Service Usage Consumer
- Needed to test if APIs are active
Full YAML source
name: Deploy to Firebase on merge on: push: branches: - main jobs: build_and_deploy: runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 - name: Use Node.js uses: actions/setup-node@v4 with: node-version-file: './.nvmrc' - name: Generate .firebaserc run: | ./write-firebase-config.sh cat .firebaserc env: FIREBASE_PROJECT_ID: ${{ vars.FIREBASE_PROJECT_ID }} FIREBASE_APP_ID: ${{ vars.FIREBASE_APP_ID }} FIREBASE_STORAGE_BUCKET: ${{ vars.FIREBASE_STORAGE_BUCKET }} FIREBASE_API_KEY: ${{ vars.FIREBASE_API_KEY }} FIREBASE_AUTH_DOMAIN: ${{ vars.FIREBASE_AUTH_DOMAIN }} FIREBASE_MESSAGING_SENDER_ID: ${{ vars.FIREBASE_MESSAGING_SENDER_ID }} FIREBASE_USE_EMULATORS: false - name: Install Firebase CLI run: | npm install -g firebase-tools firebase --version - name: Build Angular app run: | ./gradlew webApp:buildProductionWebApp - name: Deploy hosting run: | echo "${{ secrets.FIREBASE_SERVICE_ACCOUNT_BASE64 }}" | base64 --decode > "google-application-credentials.json" firebase deploy --only hosting --non-interactive rm -rf "google-application-credentials.json" env: GOOGLE_APPLICATION_CREDENTIALS: "google-application-credentials.json" - name: Deploy Firestore rules run: | echo "${{ secrets.FIREBASE_SERVICE_ACCOUNT_BASE64 }}" | base64 --decode > "google-application-credentials.json" firebase deploy --only firestore:rules --non-interactive rm -rf "google-application-credentials.json" env: GOOGLE_APPLICATION_CREDENTIALS: "google-application-credentials.json" - name: Cleanup credentials if: always() run: | rm -rf "google-application-credentials.json" Happy Firebaseing!!
Top comments (0)