Categories
DevOps

(CI/CD) Multi-Environment deployment for a React App on Firebase, using GitHub Actions

Deploying to Firebase has never been easier. Here, we’ll cover how to automate your workflow, in order to test & deploy to multiple environments. This will assist in rapid iterations to your app, without constantly pushing breaking changes to production. The goal is to have an environment for your users (production), an environment to approve changes prior to shipping (staging) and an environment that’s actively being worked on (development).

By the end of this article, your app will automatically be tested & deployed to a specific Firebase Project (one per environment) simply by pushing to GitHub. Although we go extremely in-depth in this article, the end result is rather simple. The commits can be seen here and are kept to one commit per step.

For the sake of this article, we’ll assume you’re unfamiliar with a few terms.

  • Multi-Environment Deployment: The standard practice for developing & testing changes in an isolated environment.
  • CI/CDContinuous Integration is the practice of merging all outstanding changes in a codebase, as often as possible. Being sure to test at all points. Continuous deployment is the practice of automating deployments to your users. Getting them new updates regularly.
  • Firebase: A mobile and web application development platform, owned by Google, that provides a number of products for any real world app. In this article, we’ll only be using “Firebase Hosting”.
  • Github Actions: Allows you to create different workflows in order to automate certain tasks, such as CI/CD.
  • GitFlow: A widely adapted structure for naming your git branches.

There are a few articles on deploying a React App to Firebase, using GitHub Actions. This article aims to build upon that, adding multi-environment deployment. In addition, we opted not to use any pre-existing GitHub Actions for Firebase, in order to reduce the level of abstraction and allow you to see exactly what’s happening.

Prerequisites

  • Firebase Projects set up. You will need one project per environment. (IE: App Dev, App Staging, App Production)
  • Firebase CLI installed
  • GitHub repo, for running actions.

If you enjoy diving deeper to get a better understanding of what’s happening, I recommend you take a look at Github ActionsConfiguring a workflowWorkFlow SyntaxContexts and Expressions.

Step 1) Initialize your react app

view commit

Nothing special here, feel free to skip this, if you’ve already got your react app set up.

npx create-react-app myapp

Step 2) Initialize Firebase Hosting

view commit

cd myapp
firebase init
# Which Firebase CLI features do you want to set up for this folder?
# Firebase Hosting
# Select a default Firebase project for this directory
# (recommend you use your "dev" project for now, but any is fine)
# What do you want to use as your public directory?
# build
# Configure as a single-page app (rewrite all urls to /index.html)?
# y
# File public/index.html already exists. Overwrite?
# n

Now, merge the .gitignore file Firebase uses with the one React automatically created. You can find the file here. Just copy and append it to the one you have locally. This is needed, at the time of writing, since Firebase doesn’t automatically overwrite your current .gitignore.

Step 3) Initialize CI&CD w/Github Action

view commit

First, you’ll need to generate a Firebase CI token. This is needed in order for GitHub to authenticate to Firebase. Allowing you to deploy.

firebase login:ci

Caution: this token can be used to perform almost any task on any Firebase project you have access to. Save it. If, for any reason, your token becomes compromised, you can run the following command: firebase logout --token YOUR_TOKEN

Now, you’ll need to add your Firebase token to your repo’s secrets. Navigate to your GitHub repo, go to “Settings -> Secrets -> Add a New Secret”.

Name: CI_FIREBASE_TOKEN

Image for post
Github Secrets

Note: Alternatively, you can name this FIREBASE_TOKEN, if you want to avoid passing the token explicitly during the deployment. more here.

Now that we’re all set up, let’s create our initial workflow. Create a file at .github/workflows/firebase_web.yml, with the contents below.

Workflow explained

Curious as to what’s happening? We’re creating a workflow that’s triggered whenever a push is made. The workflow runs on Ubuntu. We checkout the current branch, this way the code we’re working on is the one that’s executed. Then, we install Node, which is needed by Firebase. After that, we run yarn (npm can also be used just fine) to install dependencies. Before deploying, it’s always best to test. We do that with the same test script React ships with. Only this time, we make sure to specify `CI=true`, which tells the React script that we’re working in a [CI environment](https://create-react-app.dev/docs/running-tests/#continuous-integration). From there, we run the typical “build” command that React ships with. After the build is complete, we deploy using Firebase CLI. We explicitly pass in our token (for authentication) & a message. The message consists of the GitHub commit message and a link to the workflow ran. This message will show in the Firebase console and will be useful, in knowing which commit triggered the current deployment.

Now, add firebase-tools as a dev dependency in your package.json

"devDependencies": {
    "firebase-tools": "^7.7.0"
},

You should be able to push to GitHub and see a successful action now : )

git add .
git commit -m 'Step 3) Initialize CI&CD w/Github Action'
git push

Success 🚀

Image for post
Github Workflow

And if we head on over to Firebase Console -> Hosting, we’ll see our latest deployment along with the message we configured ⚡️

Image for post
Firebase Hosting — Release History

Brief Recap

In Steps 1–3, we developed a workflow which is capable of automating our testing & deployment. If we were only concerned about one environment, our work would be done. Next, we’ll cover extending that workflow to multiple environments. We’ll follow the common pattern of 3 environments: development, staging, production.

Step 4) Implement Multi-Environment support

view commit

Option A) With GitFlow

This is the approach we recommend, as we enjoy the structure GitFlow provides. However, it does come with the added overhead of your team understanding GitFlow. If you’re not comfortable with this, feel free to use the simpler Option B below.

Here’s an outline of which branches we’d like to deploy to which environments, following GitFlow conventions.

╔═════════════════════╦═══════════════════════╦══╗
║       Branch        ║ Environment to Deploy ║  ║
╠═════════════════════╬═══════════════════════╬══╣
║ feature/**, develop ║ dev                   ║  ║
║ release/**          ║ staging               ║  ║
║ master              ║ production            ║  ║
╚═════════════════════╩═══════════════════════╩══╝

If you haven’t already, initialize GitFlow. Then, start a feature branch.

git flow init
## We kept all default options. 
git flow feature start multi-environment-support

Update your workflow (firebase_web.yml) to utilize GitFlow style branches.

Finally

Let’s edit our .firebaserc to include our 3 project aliases, instead of just a “default”. This allows us to specify the project during deployment, via the -P argument.

{
  "projects": {
    "development": "myapp-dev",
    "staging": "myapp-staging",
    "production": "myapp-production"
  }
}

You should be able to push to GitHub and see a successful action now : )

git add .
git commit -m 'Step 3) Initialize CI&CD w/Github Action'
git push

Additionally, any pushes you make should deploy to the correct Firebase project, based on the branch you’re working on ⚡️

Image for post
Firebase Hosting — Release History

All done!

Congratulations! You’re now ready to maximize your workflow by utilizing automated deployments to multiple environments 🚀🤙🏻


Optional Steps…

Step 5) Environment variables in your React App

view commit

Let’s say you wanted variables that vary depending on the environment they’re in (environment variables). This can be useful for things such as API keys, enabling/disabling features and more. React automatically configures any environment variables prefixed with REACT_APP_during build time.

To add environment variables to your React App, simply specify them in your workflow. Since you want the environment variables to be different for each environment, you’ll want to define them per deployment step.

For example, edit firebase_web.yml to include the following:

- name: Build & Deploy Dev
  ## Configure environment variables (do this on each build step)
  env:
    REACT_APP_NAME: My App - Development

These variables will now be accessible to Github. You’ll likely want them accessible locally as well. React utilizes .env files for this.

Add .env.local to the root of your app, with the following contents

REACT_APP_NAME=My App - Local

Note: .env files are already ignored in .gitignore, so we don’t have to worry about them accidentally being committed and overwriting our CI/CD steps.

Environment variables can be accessed in a React App via process.env. For example, let’s try using the one we just set up.

Edit src/App.js

<img src={logo} className="App-logo" alt="logo" />
<!-- add below -->
<h1>{process.env.REACT_APP_NAME}</h1>

Now, start your app

yarn start

Success! 🚀

Image for post
React App, with Environment Variable

Note: Since environment variables are implemented during build time, this means that any changes made to your .env files won’t be reflected until the next time you run yarn start or yarn build.

Happy Hacking! 🤓🤘🏻

Have any questions about this article, or want to discuss a project? Feel free to reach out to us at For Makers, by Makers.

Interested in the final result? Take a look at these GitHub commits.

Leave a Reply

Your email address will not be published. Required fields are marked *