Firebase preview channels and GitLab CI/CD
October 29, 2020
Recently a new feature called preview channels was released for Firebase Hosting. Since tensor5.dev is hosted on Firebase, I decided to try this new feature, and now it’s a good time to share the pipeline that I use to build, test and deploy this website, which is made with Gatsby, with source code hosted on GitLab, and uses GitLab CI/CD for continuous integration and deployment; in particular, I will focus on how to do continuous deployment using Firebase preview channels. My pipeline is for Gatsby, but it’s generic enough that it could work with any other Node.js based static site generator.
Preview channels are a new feature of Firebase Hosting that allow you to deploy your website to a temporary URL. When you host a website on Firebase it is normally available at:
https://<project_id>.web.app
https://<project_id>.firebaseapp.com
plus any custom domain that you set up; these are called the live channels. With preview channels you can deploy at URLs:
https://<project_id>--<channel_id>-<random_hash>.web.app
https://<project_id>--<channel_id>-<random_hash>.firebaseapp.com
where channel_id
is some name that you choose for your preview channel, while random_hash
is generated by Firebase and is used to distinguish different generations of your deployment. Preview channels are useful for example if you want to see how the production version of your website will look like before you actually deploy it to its final URL, or if you have multiple collaborators sending pull requests (aka merge requests in GitLab) to your Git repository and you want to review the final deployed version of their changes before merging them to master, which will then go live.
Let’s see how this can be done with GitLab. Assuming that you are familiar with the basics of GitLab CI/CD configuration, this is a simplified version of my .gitlab.yml
:
default:
image: node:alpine
before_script:
- yarn --frozen-lockfile
build:
stage: build
script: yarn build
artifacts:
paths:
- public
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
test:
stage: test
script: yarn test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
.deploy:
stage: deploy
before_script:
- npm install -g firebase-tools
deploy preview:
extends: .deploy
script: firebase --project "${FIREBASE_PROJECT_ID}" --token "${FIREBASE_TOKEN}" hosting:channel:deploy "${CI_COMMIT_SHA}"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
deploy live:
extends: .deploy
script: firebase --project "${FIREBASE_PROJECT_ID}" --token "${FIREBASE_TOKEN}" hosting:clone "${FIREBASE_PROJECT_ID}:${CI_COMMIT_SHA}" "${FIREBASE_PROJECT_ID}:live"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Except for the last one, all jobs described above run only on commits associated to merge requests, that’s what the rules if: $CI_PIPELINE_SOURCE == "merge_request_event"
mean (read more here).
A few things are needed in order for this pipeline to work correctly. First, the firebase
command that appears in the deploy jobs requires a firebase.json
configuration file in the root of your project; you can generate one by running firebase init hosting
(more here), what matters is that the public root directory (hosting.public
in the firebase.json
) matches the output directory of your static site gererator, i.e. the artifact of your build job; in my example Gatsby uses public
as output directory, hence my build
job has the path public
specified as artifact.
Second, you need to add a couple of custom environment variables to your GitLab project. Go to Settings > CI/CD and expand the Variables section. Add the variable FIREBASE_PROJECT_ID
holding your Firebase project ID; uncheck the Protect variable option or else the variable will only be visible on protected branches and not on merge requests. Next generate an access token with firebase login:ci
and add a variable FIREBASE_TOKEN
holding it; again uncheck Protect variable, but this time check the Mask variable option so that the token does not appear in job logs.
Third, in the Settings > General of your GitLab project expand the Merge requests section and set the Merge method to Fast-forward merge; we will see later why this is needed.
During the deploy stage of a merge request, the deploy preview
job will deploy the website from the public
directory (as specified in firebase.json
) to a preview channel; to make sure that each merge request has its own unique channel we use the commit SHA as channel ID. The command firebase hosting:channel:list
will show you the list of deployed preview channels: find the commit SHA in the Channel ID column and follow the corresponding link to review your website.
Once the merge request is approved and merged to master, the deploy live
job clones the content of the preview channel to the live channel. This works because we use fast-forward merges: the CI_COMMIT_SHA
variable that appears in the deploy preview
and deploy live
jobs has the same value since the merge request commit and the master branch commit are equal.
Now we have a flow where we can review a pull request, see how the deployed version of our website looks like on the browser, and be sure that once we merge it to master the exact same version of the website will go live.