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.
Top comments (1)
Great article, would be even better to have this as a video tutorial :D