Skip to content

Jonathan Wilkinson

GitHub Actions: Cache Everything

GitHub Actions1 min read

GitHub Actions is great. Adding caching makes it even better. By caching the right resources your workflows will run faster and you'll:

  • save money (or make your free allowance go further)
  • spend less time staring at spinners

The obvious candidate for caching in a typical NodeJS project is node_modules. npm install-ing dependencies can easily cost 1-3 minutes. But by restoring from a cache we can cut this to seconds.

In this tutorial we'll go over how to:

  • cache node_modules
  • skip npm install when a cached version is available
  • invalidate the cache when our dependencies change

Cache node_modules

GitHub Actions comes with a built-in action for caching: actions/cache@v2. This action will automatically cache a given file/folder at the end of a successful workflow, and restore the same at the start.

To do this the action requires 2 parameters:

  • path: the file/folder to cache
  • key: the key to use when (re)storing the cache

Our path is obviously node_modules (as this is the folder we want to cache). But our key is slightly more complicated. In our case, we only want to restore our node_modules from the cache when our dependencies haven't changed. Accordingly, we need to associate our cache with a key that changes with our dependencies, and for this we can use a hash of our package-lock.json. This way, whenever our lockfile changes, our key will change and our cache will be effectively invalidated.

All together our caching step will look like this:

.github/workflows/unit-tests.yml
1- name: Cache Node Modules
2 id: node-cache
3 uses: actions/cache@v2
4 with:
5 path: node_modules
6 key: node-modules-${{ hashFiles('package-lock.json') }}

Skip npm install

Caching node_modules won't save time on its own. For this we'll need to skip npm install. But we can only do this when we were actually able to restore a copy of node_modules from the cache.

Luckily, this is simple in GitHub Actions. We can access the outcome of our caching step through steps.{{id}}.outputs.cache-hit (where {{id}} is the id given to our caching step). We can then use an if property to conditionally skip installation where the cache has been hit.

All together, our installation step will look like this.

.github/workflows/unit-tests.yml
1- name: Install Dependencies
2 if: steps.node-cache.outputs.cache-hit != 'true'
3 run: npm install

Put everything together

All together in a simple unit test workflow, the completed workflow should look something like this:

.github/workflows/unit-tests.yml
1name: Unit Tests
2on: [pull_request]
3jobs:
4 build:
5 runs-on: ubuntu-latest
6
7 steps:
8 - uses: actions/checkout@v2
9
10 - name: Setup Node
11 uses: actions/setup-node@v1
12 with:
13 node-version: 12.x
14
15 - name: Cache Node Modules
16 id: cache-node-modules
17 uses: actions/cache@v2
18 with:
19 path: node_modules
20 key: node-modules-${{ hashFiles('package-lock.json') }}
21
22 - name: Install Dependencies
23 if: steps.cache.outputs.cache-hit != 'true'
24 run: npm install
25
26 - name: Run Tests
27 run: npm test

All done 🎉

GitHub Actions is great. Caching is simple. CACHE EVERYTHING!

© 2020 by Jonathan Wilkinson. All rights reserved.
Theme by LekoArts