Caching NuGet packages in GitHub Actions
Caching of NuGet packages can significantly speed up .NET builds in GitHub Actions, especially if your projects has many dependencies. The generic cache
GitHub action used to be the only way to do that and I described how to configure it a while back in my article for DotNetCurry Magazine. However, recent versions of setup-dotnet
GitHub action now have this functionality built in which makes the configuration even simpler.
Any existing GitHub Actions workflow for .NET projects most likely already uses the setup-dotnet
GitHub action to install the .NET SDK. When created with the template from TimHeuer.GitHubActions.Templates NuGet package , for example, this is its configuration:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.302
To enable caching of NuGet packages, you only need to set its cache
parameter to true
:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.302
cache: true
However, for this to work you need to enable lock files for your NuGet dependencies. You do that by adding the following property to each of the project files:
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
This will cause a lock file named packages.lock.json
to be generated in the folder with the project file. You need to add this file to source control.
You should also add the --locked-mode
argument to the dotnet restore
command in the GitHub Actions workflow file:
- name: Restore
run: dotnet restore --locked-mode
This will prevent the lock files from being updated during restore and make sure that the exact versions of dependencies listed in the lock files will be used.
However, if you try to run the workflow file with these changes in place, it's likely going to fail with the following error (the exact path will of course differ):
Dependencies lock file is not found in
/home/runner/work/20240726-dotnet-github-actions-cache/20240726-dotnet-github-actions-cache
. Supported file patterns:packages.lock.json
The reason being that the setup-dotnet
GitHub action expects the single packages.lock.json
lock file to be placed in the repository root folder which is rarely going to happen. In my experience, most .NET repositories consist of multiple projects, each in its own subfolder. At best, their solution file will be placed in the root folder of the repository. To account for that, you can set the path to the lock file(s) using the cache-dependency-path
parameter:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.302
cache: true
cache-dependency-path: "**/packages.lock.json"
If you set the path to **/packages.lock.json
as I did in the snippet above, all packages.lock.json
files in the repository will be found and used. It's a good choice if your workflow file builds all project files in the repository.
Now, the build should succeed again. You can also check the logs to make sure the caching is working. In the first successful build, the following log entries for the Post Setup .NET SDK
step indicate that the NuGet packages have been cached:
/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /home/runner/work/20240726-dotnet-github-actions-cache/20240726-dotnet-github-actions-cache --files-from manifest.txt --use-compress-program zstdmt
Cache Size: ~50 MB (52461352 B)
Cache saved successfully
Cache saved with the key: dotnet-cache-Linux-9287935246dcf0938da38f834ff13de067c8c0708863a007cc9e061b1bad0a22
In subsequent builds with no changes to the NuGet package lock files, the following log entries for the Setup .NET SDK
step indicate that the NuGet packages have been retrieved from cache:
Cache Size: ~50 MB (52461352 B)
/usr/bin/tar -xf /home/runner/work/_temp/38c773d8-0fc9-4d9f-8768-d3c88c688974/cache.tzst -P -C /home/runner/work/20240726-dotnet-github-actions-cache/20240726-dotnet-github-actions-cache --use-compress-program unzstd
Cache restored successfully
Cache restored from key: dotnet-cache-Linux-9287935246dcf0938da38f834ff13de067c8c0708863a007cc9e061b1bad0a22
Received 52461352 of 52461352 (100.0%), 50.0 MBs/sec
Of course, once any lock file changes, the changed NuGet packages will again be saved to cache with a new key.
You can find a working workflow file with a small sample project in my GitHub repository. I created separate commits for the changes I made to the workflow file.
Although it isn't all that complicated to configure NuGet package caching in GitHub Actions using the cache
action, it's even more convenient to use the caching support in the setup-dotnet
action instead. It's mostly preconfigured for you and therefore even easier to get working as long as your repository fulfills the requirements, i.e. uses NuGet lock files. Which is a good idea even if you're not caching NuGet packages since you can't really have reproducible builds without them.