Most .NET developers have probably never used packages.lock.json. And to be fair, NuGet doesn’t enable it by default. But there’s a pretty good reason you might want to.
Your Package Versions Aren’t Really Locked
Consider this:
<PackageReference Include="Some.Package" Version="3.0.0" />
You might think you’re telling NuGet to use exactly version 3.0.0. You’re not.
With PackageReference, NuGet actually treats this as a minimum version. If 3.0.0 isn’t available, it can resolve a newer version.
And that’s before we even talk about transitive dependencies. Your packages depend on other packages, which depend on even more packages. Under certain conditions, such as different package sources or floating versions, two restores can resolve different dependency graphs. That’s probably not something you want happening unexpectedly in production.
Enter packages.lock.json
NuGet already has a solution for this. You just need to enable it. Add this to your .csproj:
<PropertyGroup> <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile></PropertyGroup>
Now run:
dotnet restore
NuGet generates a packages.lock.json file containing the exact resolved versions of your direct and transitive dependencies, along with their content hashes.
Commit that file to your repository. Now here’s the interesting part. In your CI/CD pipeline, use:
dotnet restore --locked-mode
This tells NuGet to restore using the lock file without modifying it. If your package references and lock file don’t match, the restore fails instead of silently regenerating the dependency graph.
When you intentionally update dependencies, run a normal restore and commit the updated lock file.
Should You Always Use It?
Not necessarily. Lock files make the most sense for applications where you control the final dependency graph. For libraries consumed by other projects, the consuming application ultimately determines which dependency versions get resolved. A library’s lock file doesn’t force those versions onto its consumers.
Also, remember that a lock file doesn’t pin your .NET SDK version. If you need that level of consistency, consider using global.json as well.
Final Thoughts
Your .csproj describes which package versions NuGet is allowed to resolve. Your packages.lock.json records which versions it actually resolved.
That’s a pretty important difference and considering how easy it is to enable, I’m surprised more .NET applications aren’t using it.
Affiliate promo
If you love learning new stuff and want to support me, consider buying a course from Dometrain using this link: Browse courses – Dometrain. Thank you!
Leave a Reply