How to set up Cloud Build for Firebase Cloud Functions

Ertan Dogrultan
Google Cloud - Community
4 min readOct 5, 2018

At Soru, a part of our system runs on Firebase Cloud Functions and Firebase Hosting. Our setup consists of various environments such as staging and prod. We needed a simple CI/CD solution, so we decided to use Google Cloud Build.

In this post, we will go over how to deploy a Cloud Function via Cloud Build configuration. Our progressive web app is built and deployed to Firebase Hosting using the same exact principles.

Note that most of this content exists in the official Google documentation i.e. here and here, but there were a few gotchas. We wanted to give you a working example that is tested and deployed.

Build the Firebase SDK

First, you need to build the firebase SDK for your project so you can call firebase deploy from the Cloud Build configuration. This is a one time thing that you need to do, so you can keep it in a separate repo if you like.

Here is the Dockerfile:

# Node 6
FROM node:boron
# install Firebase CLI
RUN npm install -g firebase-tools
ENTRYPOINT ["/usr/local/bin/firebase"]

If you are using Node 8 runtime for your firebase functions, you will get syntax errors while using await/async or other Node8 specific features during deployment. The reason is the node version we used in the Dockerfile above is actually Node 6. This is not really stated anywhere in the Google Cloud documentation, so it might come in handy. The right Dockerfile for Node 8 would be:

# Node 8
FROM node:carbon
# install Firebase CLI
RUN npm install -g firebase-tools
ENTRYPOINT ["/usr/local/bin/firebase"]

Here is there config.yaml

steps:
- name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', 'gcr.io/[PROJECT_ID]/firebase', '.' ]
images:
- 'gcr.io/[PROJECT ID]/firebase'

Then you run

gcloud builds submit --project [PROJECT_ID]--config=config.yaml .

This builds and stores the image in the Google’s private Container Registry for later use.

The bad solution

In order to run firebase deploy from a service account, we need to generate an access token. Run the following command and note the token.

firebase login:ci

At this point, it’s very straightforward to write the Cloud Build configuration that we will use in the Build Trigger.

steps:
- name: 'gcr.io/cloud-builders/npm:node-6.14.4'
args: [ 'install' ]
- name: 'gcr.io/[PROJECT_ID]/firebase'
args: [ 'deploy', '--project', [PROJECT_ID], '--token', '[TOKEN]']

Please note the node-6.14.4 tag while running npm install. I kept on getting the following error because the default uses node-8.12.0 which is not compatible with my setup.

Step #1: Error: Error parsing triggers: Failed to load gRPC binary module because it was not installed for the current system
Step #1: Expected directory: node-v48-linux-x64-glibc
Step #1: Found: [node-v57-linux-x64-glibc]

As you can see there is an obvious problem if you want to check in the configuration above. You should never let your access tokens in your source control. Therefore, I strongly recommend NOT to use the script above. Instead you can store your token in the Google Cloud Key Management Service and pull it as a secret environment variable in your Cloud Build configuration.

The good solution

First we need to create a key chain and a key.

gcloud kms keyrings create [KEYRING_NAME] \
--location global \
--project=[PROJECT_ID]
gcloud kms keys create [KEY_NAME] \
--location=global \
--keyring=[KEYRING_NAME] \
--purpose=encryption \
--project=[PROJECT_ID]

Now, we need to use the token generated by firebase login:ci and assign it to a environment variable.

export FIREBASE_TOKEN=[TOKEN]

If you check in or distribute the token by mistake, you can always revoke it by doing firebase logout --token [TOKEN]. Otherwise, these tokens do not have expiration time, so they live forever.

Lastly, you need to encrypt the token so that we can use the encrypted version in our configuration.

echo -n $FIREBASE_TOKEN | gcloud kms encrypt \
--plaintext-file=- \
--ciphertext-file=- \
--location=global \
--project=[PROJECT_ID] \
--keyring=[KEYRING_NAME] \
--key=[KEY_NAME] | base64

Don’t forget to put the project id in the encryption script. The default would work if you have a single project. However, things might get confusing if you have different projects for different environments.

At this point you have an encrypted base64 string that you can distribute safely.

The new configuration for Cloud Build looks as follows:

steps:
- name: 'gcr.io/cloud-builders/npm:node-6.14.4'
args: [ 'install' ]
- name: 'gcr.io/[PROJECT_ID]/firebase'
args: [ 'deploy', '--project', '[PROJECT_ID]']
secretEnv: ['FIREBASE_TOKEN']
secrets:
- kmsKeyName: projects/[PROJECT_ID]/locations/global/keyRings/[KEYRING_NAME]/cryptoKeys/[KEY_NAME]
secretEnv:
FIREBASE_TOKEN: [ENCRYPTED_TOKEN]

One problem I encountered happened when I wrote the deployment step as follows:

- name: 'gcr.io/[PROJECT ID]/firebase'
args: [ 'deploy', '--project', '[PROJECT ID]', --token, '$$FIREBASE_TOKEN']
secretEnv: ['FIREBASE_TOKEN']

My configuration was based on the documentation, but for some reason, this way of passing the token did not work, but simply setting the environment variable $FIREBASE_TOKEN was sufficient, so you can remove the --token argument. (I still don’t know why this wouldn’t work.)

One last remark before wrapping up: Make sure that the service account <id>@cloudbuild.gserviceaccount.com has roles/cloudkms.cryptoKeyDecrypter (Cloud KMS CryptoKey Decrypter) role so that it can decrypt the token.

Setting up the Build Trigger

Ok, at this point you have everything you need. For the sake of completeness, I’ll show how to set up the build trigger on the console as well.

  • Go to GCP Console and search for Build Triggers
  • Add Trigger and choose your repository hosting option. Ours is GitHub.
  • After authentication, you’ll see the following configuration page.

This says, when we merge the code to master branch, the configuration /cloudbuild/staging.yaml in the repo triggers and deploys the service to staging environment.

You can configure your code such that any code checked in to any branch can kick off a test pipeline as well. We will show you how to hook up the Cloud Build results to your GitHub repo in a separate blog post so that you can block merging on build/test failures.

I hope this helps and feel free to suggest improvements.

Happy coding!

--

--