How do I cache steps in GitHub actions?
Most use-cases are covered by existing actions, for example:
actions/setup-node
for JSdocker/build-push-action
for Docker
Custom caching is supported via the cache action. It works across both jobs and workflows within a repository. See also: GitHub docs and Examples.
Consider the following example:
name: GitHub Actions Workflow with NPM cache
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache NPM dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-npm-cache-
- name: Install NPM dependencies
run: npm install
How caching works step-by-step:
- At the
Cache NPM dependencies
step, the action will check if there's an existing cache for the currentkey
- If no cache is found, it will check spatial matches using
restore-keys
. In this case, ifpackage-lock.json
changes, it will fall back to a previous cache. It is useful to prefix keys and restore keys with the OS and name of the cache, as it shouldn't load files for a different type of cache or OS. - If any cache is found, it will load the files to
path
- The CI continues to the next step and can use the filed loaded from the cache. In this case,
npm install
will use the files in~/.npm
to save downloading them over the network (note that for NPM, cachingnode_modules
directly is not recommended). - At the end of the CI run a post-action is executed to save the updated cache in case the
key
changes. This is not explicitly defined in the workflow, rather it is built into the cache action to take care of both loading and saving the cache.
You can also build your own reusable caching logic with @actions/cache such as:
- 1-liner NPM cache
- 1-liner Yarn cache
Old answer:
Native caching is not currently possible, expected to be implemented by mid-November 2019.
You can use artifacts (1, 2) to move directories between jobs (within 1 workflow) as proposed on the GH Community board. This, however, doesn't work across workflows.
The cache
action can only cache the contents of a folder. So if there is such a folder, you may win some time by caching it.
For instance, if you use some imaginary package-installer
(like Python's pip
or virtualenv
, or NodeJS' npm
, or anything else that puts its files into a folder), you can win some time by doing it like this:
- uses: actions/cache@v2
id: cache-packages # give it a name for checking the cache hit-or-not
with:
path: ./packages/ # what we cache: the folder
key: ${{ runner.os }}-packages-${{ hashFiles('**/packages*.txt') }}
restore-keys: |
${{ runner.os }}-packages-
- run: package-installer packages.txt
if: steps.cache-packages.outputs.cache-hit != 'true'
So what's important here:
- We give this step a name,
cache-packages
- Later, we use this name for conditional execution:
if
,steps.cache-packages.outputs.cache-hit != 'true'
- Give the cache action a path to the folder you want to cache:
./packages/
- Cache key: something that depends on the hash of your input files. That is, if any
packages.txt
file changes, the cache will be rebuilt. - The second step, package installer, will only be run if there was no cache
For users of virtualenv
: if you need to activate some shell environment, you have to do it in every step. Like this:
- run: . ./environment/activate && command