Deploying Nx Affected Apps from GitHub Actions

Posted on November 05, 2020

Written by Preston Lamb

angular

tldr;

Having multiple apps in a Nx workspace is common and convenient, but when it’s time to deploy the apps it can be difficult to only deploy the correct apps. Sure, you can deploy all the apps, but that’s unnecessary and can cost more money by having CI/CD servers running for longer periods of time. If an app hasn’t changed, it shouldn’t be deployed. With Nx’s tools, we can find out which apps are affected by a certain change, and only deploy those apps.

My Use Case

First, a little background on my workspace and how we deploy our apps. We build our apps with Google Cloud Build and put them in a Docker image. We then deploy those images on Google Cloud with Kubernetes. For a long time, we deployed every merge to master to our test environment. We then manually deployed to production by creating a tag prepended with prod_app_1 or prod_app_2. When Google Cloud Build is notified of one of those tags, the build is kicked off, the image created, and deployed. We decided though that we didn’t want to deploy all apps to test each time we merged into master. The decision was that we would tag affected apps in the same manner as production, but prepending the string with test. That’s what I’ll show in this blog post.

With that being said, you should be able to adapt this method to your use case. I’ll point out the places that you can change for your needs. You will have to figure out the exact details for how to deploy, but I’ll try and get you at least part of the way there.

Quick Review

Nx comes with the ability to run certain commands on only affected parts of the workspace. Some of those commands provided are build (which you could use for this situation, although I won’t be), lint, and test, for example. In my last post, I wrote about how you could run tests on the affected parts of your app using GitHub Actions. This post adds to that one. If you need more information on running Nx affected commands, check out the docs.

Nx Builders

The first step to accomplish my goal is to understand Nx Builders. According to the docs, a builder is something that performs actions on your code. Builders encourage consistent output of actions run on the code. In addition, you can use nx affected on your workspace and run commands if that library or app was affected by the change. Nx provides a builder called run-commands. With it, you can create custom targets that can be run with the Nx affected command on your code. I’ve found that this is, in many cases, sufficient for my needs. If your builder is more complicated, you may need to create a custom builder. You can learn more about that in the Nx docs.

Back to using the run-commands builder. In my case, I decided to run a custom affected target using the run-commands builder. I called it test-release, which means I can run the following when code is merged into the master branch:

nx affected --target=test-release

When this command is run, Nx looks at the codebase and determines which apps are affected by the changes. If an app was affected, it runs the command that is referenced in the custom target. This custom target is added in the angular.json file. In this file, there’s a projects attribute where all the libraries and apps in an Nx workspace are placed. The name of each library or app is a key on the projects object. There is a lot of information about the app or library, most of which we don’t need to use. If we want to add our custom target, we can add a key to the projects.app-name.architect object. That would look like this:

{
  "projects": {
    "my-app": {
      "architect": {
        "test-release": {
          "builder": "@nrwl/workspace:run-commands",
          "options": {
            "commands": [
              {
                "command": "npm run test:release:my-app"
              }
            ]
          }
        }
      }
    }
  }
}

In this example, we added a custom target called test-release that we can run on apps in our workspace. The command there can be anything that you want to do. In this case, we’re running an npm script if the app is affected. We can run the target manually like this:

nx run test-release my-app

Or run it on all affected apps like this, as mentioned above:

nx affected --target=test-release

Now that we have our custom target set up using the run-commands builder, we can move on to the GitHub Action workflow creation where this custom target will be run.

GitHub Action Workflow

In this section, we’ll talk about the needed action workflow file necessary to run our release command on affected apps. I’ll provide the full workflow file first, and then we’ll walk through it piece by piece.

name: Nx Affected Test Release

on:
  push:
    branches: [master]

env:
  BEFORE_SHA: $

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Use Node.js 12.x
        uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - run: git fetch origin master
      - name: npm install
        run: npm install
      - name: Run Affected Test-Release
        shell: bash
        run: npm run affected:test-release -- --base=$BEFORE_SHA

Let’s look at the first few lines of the workflow:

name: Nx Affected Test Release

on:
  push:
    branches: [master]

First, we give the workflow a name. That can be anything that you’d like to use to identify this workflow. Next, we determine when the workflow will run. In this case, we want the workflow to run any time the master branch gets new pushes.

env:
  BEFORE_SHA: $

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Use Node.js 12.x
        uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - run: git fetch origin master
      - name: npm install
        run: npm install

In this section, we first create an environment variable to use throughout our workflow. This variable is storing the commit SHA from before the last push (or merged pull request) occurred. We’ll need this value later on. Next, we define the jobs that we’ll run. We chose to run the job on the latest ubuntu machine. The final section is the steps of the workflow. First, we check out the repo, using the fetch-depth of 0. This will get the full git history, which we’ll need to be able to run the nx affected command. The next step sets the Node version we’re using for the run to 12.x. Next, git fetch gets the information we need about other branches and tags in the repository. Again, this is necessary for running the affected command. The final step here is running npm install. All node_modules must be installed for the affected command to work.

Let’s look at the final step of the workflow:

- name: Run Affected Test-Release
  shell: bash
  run: npm run affected:test-release -- --base=$BEFORE_SHA

This is the meat of our workflow, the whole reason for running the workflow in the first place. Here we’re running the affected command with the test-release target. There is one part that’s different here, though. Because we’re on the master branch, if we just ran npm run affected:test-release there would never be any changes noticed. That’s because the affected command uses two flags, --base and --head. The base is the branch to compare against, and head is where we’re currently at in our git history. In this case, those two locations in the git history would be the same. To get the result we want, we need to manually set the base flag. We can do that with the --base=$BEFORE_SHA flag. $BEFORE_SHA, you’ll remember, was set earlier in our workflow. It’s a variable that GitHub provides us when running workflows. With that flag, we’ll now compare our current location, master, to the last commit before the pull request was merged or the last push to master. That way Nx can effectively check for differences in our code and run the command on the affected apps.

If the changes that were made to the codebase affected an app, the command from our custom target will be run. Remember, we defined the custom target above. That command is what will deploy your app. In my case, that command is what creates the proper tag and pushes it to the repository. For you, the app could then be built and pushed to a remote server, for example. This is where you will need to alter the workflow to meet your needs. Everything else up to here, however, should work for you the same as it did for us.

Conclusion

With this workflow and the custom target, we went from deploying our apps to test even when they hadn’t changed to only deploying affected apps. It’s saving us time on Google Cloud Build, and ensuring that nothing accidentally changes due to a new package version, for example. GitHub Actions have been perfect for this use case, and we are really happy with the outcome. It took some trial and error, some work, and a couple false starts, but it paid off. If you need to deploy only certain apps in your Nx workspace, feel free to create a GitHub Action using the workflow file above.

Click here to subscribe to the newsletter and be the notified when a new blog post is available!