
Why aren’t packages.lock.json files the default in .NET projects?
I recently went down the rabbit hole of NuGet version locking and was surprised by how little attention packages.lock.json seems to get in .NET compared to lock files in other ecosystems like node.
A typical PackageReference may pin your direct dependency, but it doesn’t freeze the entire resolved dependency graph. Transitive dependencies are still resolved during restore.
NuGet already has everything needed to make this deterministic:
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
Commit the resulting packages.lock.json, and then in CI:
dotnet restore --locked-mode
That gives you a frozen direct + transitive dependency graph, including package content hashes. If the graph changes without the lock file being deliberately updated, CI fails instead of silently accepting it.
What I also hadn’t appreciated before looking into this is that Central Package Management and lock files solve different problems:
Directory.Packages.props -> centrally manages the versions you specify
packages.lock.json -> records the fully resolved dependency graph
So using CPM doesn’t really make the lock file redundant.
We’ve started looking at this more seriously from the reproducible-build / supply-chain side rather than just dependency management.
I’m curious how people here handle this:
Do you commit packages.lock.json for applications? And if not, is there a particular reason you prefer NuGet to resolve the graph on restore?
I wrote up what I learned, including locked-mode CI, CPM/transitive pinning and the supply-chain implications:
https://vensas.de/blog/nuget-version-locking
Disclosure: I work at vensas, where the article is published.