Automate Firebase deploys with Github Actions

Tomáš Pustelník

14 January, 20198 min read

Deploying a website or an app can sometimes be a painful process. Especially if we don't have any automated workflow. And setting up proper CI may be a daunting task as well. That is where Github steps in with their new feature called Github Actions1.

Github Actions are something like CI but integrated right into the Github with a fairly simple and straightforward setup.

What are we going to do

We have a website built by static site generator (like Jekyll or Gatsby) in our Github repo. When we push some code to a master branch, we want our website to be built and re-deployed to keep it up to date. As a hosting solution, we will use Firebase Hosting from Google. It's easy to set up and cheap hosting solution for static files with https certificate by default and Google infrastructure behind it.

Note: Firebase offers actually a lot more than just hosting, but that is not in the scope of this article.

But first, we will have a closer look at Github Actions and what they are.

Anatomy of Github Actions

What exactly are the Github Actions, you ask? Github Actions as a feature allows you to build an automatic workflow consisting of several steps (hard limit is 100 steps at the moment). This workflow may be triggered by git events like a push to the repo, changes in pull requests and a lot more.

These individual steps are called Actions and they are small modules, usually with single responsibility (like build, setup database, run tests, etc.). Actions are grouped into Workflow where you choose event a workflow should respond to and what action it should trigger. You will usually define only one action in workflow, the rest of the actions will be chained via needs configuration in action's body. An example is worth more than a thousand words so have a look at simple workflow definition:

workflow "Deploy to Firebase" {
  on = "push"
  resolves = ["Deploy"]
}

action "On master branch" {
  uses = "actions/bin/filter@master"
  args = "branch master"
}

action "Build" {
  uses = "./.github/gatsby-build"
  needs = ["On master branch"]
}

action "Deploy" {

Example of a workflow definition

We can code our workflow in a text editor, or we can use Github's visual editor which allows us to build our workflows with drag and drop and change of few fields. In the end, all workflows are just a text file with a specific syntax.

Github Actions editor options
Comparison of Github Actions in visual and text editor.

Note: Unless you want to use only predefined actions (official Github or other public ones) you will most likely have to use a code editor at some point.

The most important part of action definition is the uses option which defines what the action will do. Other parameters like args and secrets (we will talk about them in the next section) allow us to tweak the action's behavior. uses may contain a link to existing action definition (which is just a reference to public repo with action)2. It may also be a reference to docker image (from official docker registry) or relative path to local action in your project repo. More detailed information about this can found in the documentation.

Creating our custom action

Using existing action or docker image is not that difficult, we are more interested in the creation of our custom action. So let's get started. We will reuse workflow definition from the previous section:

...

action "Deploy" {
  # TODO - this is what we will create
}

.github/main.workflow file

After initial hype, we found out that Firebase is not among the default prepared actions. But fortunately, that's not a big deal since creating a custom action is quite a simple process.

First, create a folder where will our brand new action live. It may be anywhere in the project structure. You may put it even in hidden .github folder close to your main.workflow file.

Project folder structure
Example of folder structure with Github Actions

In this folder create a simple Dockerfile and shell script named entrypoint.sh. We will have a look at each of them.

Note: For this simple action you really do not have to have a huge understanding of Docker. But in case you will be creating more advanced actions, I would suggest having a look at Docker documentation

Dockerfile

FROM node:10

ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["--help"]

Dockerfile

Let's start with Dockerfile first.

The first line with FROM instruction says we want our Docker image to be derived from Node.js image with Node version 10.

ADD moves out entrypoint.sh file to Docker container filesystem.

Now comes the important part. ENTRYPOINT allows us to define what should the container do once it's loaded and started (in our case we run the script entrypoint.sh). This instruction can be overwritten in Github Actions by runs option in action.

And finally last CMD instruction contains arguments which will be passed to ENTRYPOINT script. We included --help as a default value to give the user some hints how to use the action. That is a suggested option from Github Actions documentation as well. Value of this instruction can be overwritten with args option in action. For a more in-depth explanation, you can read the official documentation.

Entrypoint.sh

#!/bin/sh -l

set -eu

echo "Deploy to firebase"
sh -c "npx firebase deploy $* --token $FIREBASE_AUTH_TOKEN"

entrypoint.sh file

Now for our entrypoint script. This one will be fairly simple. set -eu ensures that action fails if there is a problem with the execution of the script. echo just prints a text to console.

The most important part of the script is the last line. sh -c means we want to execute shell command passed as a parameter. In our case that is "npx firebase deploy $* --token $FIREBASE_AUTH_TOKEN". Notice the $* symbol in the command. This symbol will be expanded to parameters passed from CMD instruction in Dockerfile (or from args option in action's body). Github secrets and environment variables can be accessed as usual with a dollar sign and the name of the variable (like $FIREBASE_AUTH_TOKEN in our case).

Note: You have to have firebase-tools installed as dev dependency in your project to use it in the action.

Using our new action

Great we have our first custom action ready. And it wasn't that hard, right? Just as I promised. All that is left is to use it in our workflow.

...

action "Deploy" {
  needs = "Build"
  uses = "./our-cool-custom-action"
  secrets = ["FIREBASE_AUTH_TOKEN"]
  args = "--only hosting"
}

Body of our Firebase deployment action.

See the args option in our Deploy action? We pass arguments to our action and override CMD instruction in our Dockerfile. So the line from our entrypoint.sh script will be during execution expanded to:

npx firebase deploy --only hosting --token OUR_SECRET_TOKEN_TO_CONNECT_TO_FIREBASE

The result of parameter expansion in the deployment script.

In this case, we deploy only static files (hosting option). But we can use the same action to deploy cloud function or other sorts of things Firebase allow us.

Thanks to this our action is pretty reusable as well. So instead of storing it locally in our project repo and referencing it with a relative path like in the example above. We can create a new public repo for our action and store it there. Referencing from public repo would look like this:

uses = "{user}/{repository_name}@{commit_hash or branch_name}"

Pattern for referencing Github Action published in public repository.

Conclusion

And voila you have learned how to do your custom Github Action 🎉🎉🎉. You can push new commits to your repo and watch how the magic happens without you lifting a finger.

You can use what you have learned today to create your own custom actions which will help you to be more productive. The sky is the limit here. If you are interested in learning more about Github Actions see the full documentation at Github.

Have fun and enjoy your new superpowers.

Final note: using Filter action to react only for a push to the master branch will cancel all other actions in the workflow. This will cause Github checks in pull requests to fail. I wasn't able to find a way how to avoid this and Github documentation suggests this is standard behavior. For these reasons, you may have a problem to merge PRs to master if your repo has set Github checks as mandatory to merge PR. There is even open issue for this

Update: workaround was posted on GitHub forum. Kudos to NiklasMerz for figuring this out.


If you are using Firebase for your projects and do not feel like writing a custom Github Action feel free to use the one we created. Just put snippet below into your actions body and you are done.

action "Deploy" {
  uses = "webscopeio/firebase-deploy-github-action@master"
  secrets = ["FIREBASE_AUTH_TOKEN"]
  args = "--only hosting"
  ...
}

Example snippet of how to user Webscope's Firebase deploy action.


  1. In time of writing this article Github Actions are still in public beta and available only to private repos.

  2. Github provides several actions for some most popular services out of the box.


Tomáš Pustelník

Senior frontend developer, HTML/CSS expert and accessibility advocate at Webscope.io. Experienced with JS, React, React native and Firebase with overlap to design and UX as well. Tooling addict. Always love to learn new stuff in and out of IT field.

Want to hire us?

hello@webscope.io