Continuous Integration and Deployment with Google Cloud Build
Continuous Integration and Delivery, aka CI/CD, aka DevOps is the process of automating build, test, and deploy tasks between code changes to your software. The practice can yield a wide range of benefits, but most importantly it keeps your development code looking nearly identical to your production code. The adoption of devops has been a macrotrend in tech for the last few years and presents opportunites for both large teams and independent entrepreneurs.
In the following lesson, you will learn how to use Google Cloud Build to automate the following tasks by simply committing your code to Github:
- Run unit tests
- Build the production code
- Deploy to Firebase Hosting
Step 0: Prerequisites
- Install firebase-tools CLI.
- Install gcloud CLI.
- Be able to create a frontend of your choice like Angular, Vue, or React.
- Billing must be enabled on Google Cloud, but the service is free for 120 minutes of build time per day (that’s a lot).
Step 1: Build an App
You can follow along with this lesson using your existing Firebase project or by starting from scratch. The frontend code does not really matter - it just needs to be an NPM project that we can build and test. Below are some popular options to quikly create a frontend JS app from the command line.
# pick your poison ☠️
ng new my-app
vue create my-app
npx create-react-app my-app
cd my-app
firebase init
# select hosting
All of these frameworks ship with basic automated specs and build commands. This is what we want to automate with Cloud Build.
npm run test
npm run build
firebase deploy
Step 2: Create a Remote Git Repo
At this point, we need to connect your code to a remote git repo. This lesson will be using Github, but Cloud Build also integrates with GCP Source Repositories and BitBucket.
Create a Github repo (can be public or private), then commit your initial code to it.
git add .
git commit -m "initial commit"
git remote add origin git@github.com:<your-repo>.git
git push -u origin master
Step 3: Grant Cloud Build IAM Permissions
Activate the Cloud Build API on GCP.
Next, give Cloud Build access to your Firebase project. Go to the IAM menu and find the service account for @cloudbuild.gserviceaccount.com. Go ahead to grant permission to KMS Cryptokey Decrypter and Firebase Admin and the API Keys Admin.
Step 4: Build Steps
Now it’s time for the fun part, writing out the build steps in the cloudbuild.yaml file, but first we need a Docker container with firebase-tools
installed.
Upload the Firebase Builder
Firebase is not available in of the default NPM image on GCP, but we can use a community builder. You can add it to your GCP project with the following steps:
git clone https://github.com/GoogleCloudPlatform/cloud-builders-community
cd cloud-builders-community/firebase
gcloud builds submit --config cloudbuild.yaml .
# wait for it to finish, then cleanup...
cd ../..
rm -rf cloud-builders-community
Now we should see the image in the container registry.
Define the Build Steps
Create a file named cloudbuild.yaml
in the root of the project. It will tell Cloud Build the steps required to build and test your code.
The build takes place in four steps. Notice how the name
field points to a container with a cooresponding command, i.e npm or firebase. Keep in mind, there are many additional options you can pass to these steps.
- Install NPM dependencies
- Run unit tests
- Run production build
- Deploy
steps:
# Install
- name: 'gcr.io/cloud-builders/npm'
args: ['install']
# Test
- name: 'gcr.io/cloud-builders/npm'
args: ['run', 'test:unit']
# Build
- name: 'gcr.io/cloud-builders/npm'
args: ['run', 'build']
# Deploy
- name: 'gcr.io/$PROJECT_ID/firebase'
args: ['deploy']
Add the Build Trigger
Almost done! We just need to connect our Github repo to Cloud Build by registering the build trigger. Make sure to point the trigger the cloudbuild.yaml.
Trigger it
Now for the moment of truth…
git add .
git commit -m "feat: added CI/CD pipeline"
git push origin master
Hopefully your build succeeds, but errors are very common when setting up CI/CD for the first time. All of the logs can be found be directly in build details page, so review the last entries to see what failed.
Advanced: Secure your Environment Secrets
The env encryption steps are optional for Firebase because you can grant access via IAM. However, this is still a good pattern to follow if you need to authorize other 3rd party APIs. For example, you might want to update your Algolia Search index before deployment, which would require a sensitive API key.
There are several ways to manage environment variables for your CI builds, but I find the method outlined below to be the most flexible and it can easily scale to a large number of secret tokens.
Obtain the Firebase CI Auth Token
The command below will create an auth token that can authenticate a server into Firebase.
firebase login:ci
Let’s add a custom NPM script for deployment using this token as an environment variable.
{
"scripts": {
// ...
"deploy": "firebase deploy --debug --token \"$FIREBASE_TOKEN\"",
}
}
Create an .env file
Do not share the contents of the .env
file publicly and do not commit it to your public source code. It could be used to take destructive action on your project.
Create a file named .env
in the root of your project. It will used to manage the secret tokens (or API keys) required to build/deploy your code. Currently this is only Firebase, but it is likely you will use other APIs and will organize complexity down the road.
Lastly, let’s make sure to add this file to the .gitignore
.env
Now copy and paste the token from the previous step to the environment.
FIREBASE_TOKEN=<your-token>
Create a Keyring
We now have a secret token, so how do we transfer it to the server via a public git repo? We encrypt it.
Go the GCP console and find IAM » Cryptographic Keys. First, create a keyring for your app - it can be used to encrypt multiple keys via a single container.
Next, create a key for your secrets - let’s give it a name of cloudbuild-env.
Encrypt
Run the command below to encrypt the environment secrets into a single file named env.enc
- this file is safe to commit to the repo and can only be read by services with access to the keyring.
gcloud kms encrypt \
--plaintext-file=.env \
--ciphertext-file=.env.enc \
--location=global \
--keyring=YOUR_KEYRING_NAME \
--key=cloudbuild-env
Decrypt Env Secrets
The first step is important. It unwraps our environment variables and makes them available in the build container, allowing them to work with NPM scripts. Without this step, the server would not have authorization to interact with our 3rd party APIs. Also, make sure firebase-tools is installed in your dev dependencies npm i -D firebase-tools
.
steps:
- name: gcr.io/cloud-builders/gcloud
args:
- kms
- decrypt
- --ciphertext-file=.env.enc
- --plaintext-file=.env
- --location=global
- --keyring=myproject
- --key=cloudbuild-env
# Insert other build steps here
# Deploy
- name: 'gcr.io/cloud-builders/npm'
args: ['run', 'deploy']
Additional Pro Tips
Run a Build without a Git Trigger
Sometimes you just want to build without making a git commit. You can do this at any time from the command line:
gcloud builds submit . --config=cloudbuild.yaml
Conditionally Filter Steps in Cloud Build
It most cases, you only want to deploy your code when changes are merged into the master branch. However, you may still want to run the other CI steps to ensure all tests pass when a pull request is submitted. If you followed the advanced settings above, you can add a conditional bash statement that looks at the $BRANCH_NAME
env variable.
{
"scripts": {
"deploy": "...",
"deploy:ci": "if test \"$BRANCH_NAME\" = \"master\"; then npm run deploy; fi"
}
}
If we use the deploy:ci
command in our cloudbuild.yaml it will only deploy when the master branch changes.
Using a Custom Docker Container Builder
This very site, fireship.io, uses Google Cloud Build for CI/CD, but it cannot use the default Node or Go builders because they don’t contain the right mix of dependencies. Fortunately, we can create our own custom builder that serves as the cloud environment for building and testing our code.
FROM node:8
RUN npm i -g firebase-tools
ADD your-weird-dependencies-here
You can then deploy this image to the GCP container registry and use it in your build steps.
The End
There are many great CI/CD providers out there like Travis, CircleCI, etc, but Cloud Build is especially nice for Firebase apps because it’s a GCP service with generous pricing. It comes with 120 free build minutes per day (that’s a lot) then becomes pay-as-you-go if you manage to exceed that cap. By comparison, standalone services usually have a small free tier, then jump to $50+ per month. While it can be tedious and frustrating to configure initially, a solid CI/CD pipeline can be a huge productivity-booster in the long run.