Deploying Nx Affected Apps from GitHub Actions
Tags: angular, nx, github, javascript
Published: 2021-03-11 14:27:58

Preston Lamb | ng-conf | Nov 2020

### **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.

### **Background**
First, a little background on our 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 the app 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 instead the tag would start 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](https://www.prestonlamb.com/blog/running-nx-affected-commands-in-github-actions/), 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](https://nx.dev/latest/angular/cli/affected#affected).

### **Nx Builders**
The first step to accomplish my goal is to understand Nx Builders. [According to the docs](https://nx.dev/latest/angular/workspace/executors/using-builders), 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](https://nx.dev/latest/angular/workspace/executors/creating-custom-builders).

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. Here’s an example:

```
{
  “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:

```
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: ${{ github.event.before }}
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: ${{ github.event.before }}
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 stores 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 will be 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 pulls the full git history, which is needed 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 whole reason for running the workflow in the first place. In this step, 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`, if you recall, was set earlier in our workflow. It’s a variable that GitHub provides us when running workflows. With that flag, we can 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 responsible for creating the proper tag and pushing 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.

---

## **ng-conf: The Musical is coming**
ng-conf: The Musical is a two-day conference from the ng-conf folks coming on April 22nd & 23rd, 2021. Check it out at [ng-conf.org](https://www.2021.ng-conf.org/)

###### Thanks to Jim Armstrong and Erik Slack.