Dependency Management Revisited: Lockfiles, Vendoring, and What They Actually Guarantee

What Led Me Here
You push a change that works locally. The tests pass. CI runs and fails. You rerun the pipeline without touching the code, and this time it succeeds.
Nothing changed, yet the outcome did.
Some of us assume our dependencies will just work. We install packages, commit a lockfile, push changes to a repository, and let CI run. Until one day, the same code behaves differently on another machine, in another container, or after a seemingly harmless update.
That tension sits quietly in most workflows. We work around it without ever fully interrogating it.
The question that pulled me in was not how to use the tools but why they exist in the first place: What problem were they actually designed to solve, and how far do their guarantees actually reach?
That line of thinking started when I watched a video by Mehul Mohan discussing the 2026 npm supply chain incident. A compromised package briefly entered the ecosystem and spread through normal dependency resolution. Developers who installed during that window pulled in a version that later disappeared or was replaced. Teams attempting to rebuild environments afterward did not always get the same result, even when their code had not changed.
What stood out was not just the security angle, but the operational consequences. Small differences in dependency management practices led to very different outcomes. Some teams rebuilt safely; others discovered their builds depended on the external state they did not fully control. That observation reframed the problem for me. Dependency management is not only about installing packages. It is about defining the boundary of what a build can reliably reproduce.
Like many developers working across Python and Go ecosystems, I rely on tools such as pyproject.toml, uv, and Go modules. But I realized I had never fully examined the mechanics behind them. In particular:
Why do lockfiles exist in the first place?
What does vendoring actually give you that a lockfile does not?
What are teams really protecting against when they choose one approach over the other?
In Go, for example, I had used commands like go mod vendor without deeply considering the underlying trade-offs: bandwidth vs determinism, convenience vs control, speed vs reproducibility.
That curiosity led me down a rabbit hole. This article is the distilled version of what I found.
The Root Problem: Dependency Specifications Are Intentionally Vague
Before getting into lockfiles or vendoring, it helps to understand why the problem exists at all.
When you declare a dependency in a project file, whether that is a package.json, pyproject.toml, or go.mod, you are rarely specifying an exact version. You are specifying a range.
"requests": "^2.28.0"
That caret (^) symbol means:
install 2.28.0 or any compatible newer version, up to but not including 3.0.0.
Similarly:
~or tilde allows patch updates (e.g., 2.28.1 to 2.28.9)No prefix means an exact version
This is intentional. Version ranges let you absorb bug fixes and security patches automatically, without manually updating every dependency. But they introduce a tradeoff: two installs from the same config file, at different times, can produce different results.
This is not hypothetical. It happens regularly when:
A dependency releases a new compatible version
A package registry updates metadata
A CI pipeline runs days or weeks after the last local install
The result is a subtle form of environmental drift. Your pyproject.toml or package.json is a recipe, but one that can produce slightly different outputs depending on when it is executed.
Lockfiles: Pinning the Exact State
A lockfile solves environmental drift by recording the exact versions of every dependency your project resolved, including transitive dependencies (dependencies of dependencies).
Examples across ecosystems:
package-lock.json(Node.js)poetry.lockoruv.lock(Python)go.sum(Go, partially serves this role)
Where pyproject.toml says “requests, version 2.28 or compatible,” a lockfile says “requests, exactly 2.28.2, with this exact hash.”
The difference matters. When a developer installs from a lockfile, they get the same resolved graph every time, not a newly computed one.
$ uv sync
Resolved 47 packages in 0.18s
All dependencies match uv.lock
That last line is the key signal: the environment matches the recorded dependency graph exactly. This is why committing lockfiles matters.
Without them:
Developers on the same team can silently run different dependency versions
CI pipelines can fail without code changes due to upstream regressions
Debugging becomes harder because the dependency state is no longer fixed
With them:
Everyone shares the same dependency graph (dev, CI, production)
Dependency updates become explicit and reviewable changes
The exact environment can be recreated later
Vendoring: Taking Ownership of the Dependencies Themselves
Lockfiles solve version pinning. They do not fully solve availability.
What happens if the dependency is unavailable, altered, or removed?
Vendoring addresses this by copying dependencies directly into the repository.
In Go:
go mod vendor
This creates a vendor/ directory containing the full source code of all dependencies.
Then builds can be forced to use it:
go build -mod=vendor
At that point, the build no longer depends on external registries. No network access is required. No module proxy is needed. The dependency is part of the repository itself.
This becomes important in:
Air-gapped environments
Regulated systems requiring full dependency control
Disaster recovery setups where external services may be unavailable
High-risk supply chain contexts
What Each Mechanism Actually Guarantees
| Property | Lockfiles | Vendoring |
|---|---|---|
| Exact versions pinned | Yes | Yes |
| Actual source code stored | No | Yes |
| Requires network | Usually | No |
| Works if the registry is down | No | Yes |
| Works if the package is deleted | No | Yes |
| Repository size impact | Small | Large |
A useful mental model or analogy:
Lockfile is a precise shopping list
Vendoring is bringing the entire store home
Both provide determinism. Only vendoring removes external dependency on the supply chain.
The Operational Costs
Lockfiles are cheap to maintain: They are small, diffable, and usually automated. The main requirement is discipline: commit them and update them intentionally.
Vendoring has real costs: Repository bloat is the most obvious, thousands of files may be introduced, increasing clone time and CI overhead.
git diff
+ 12,843 files changed
Change visibility is reduced. Dependency updates become noisy diffs rather than clear version changes.
Maintenance becomes explicit. Updates require re-vendoring rather than simple version bumps.
None of this makes vendoring incorrect. It makes it a deliberate tradeoff.
How These Approaches Sit in Different Ecosystems
Go
go.moddefines dependenciesgo.sumverifies integritygo mod vendorcreates a vendored tree
Python
pyproject.tomldefines dependency rangesuv.lockorpoetry.lockpins exact versionsVendoring is uncommon and usually manual
Node.js
package.jsondefines version rangespackage-lock.jsonoryarn.lockpins resolved versionsnode_modulescan be treated as a form of implicit vendoring
What This Is Really About
The 2026 npm incident makes visible something that is usually invisible: the distance between declaring a dependency and controlling what actually executes in your build. Most of the time, that distance feels like nothing, until it does not.
Lockfiles exist because flexibility without memory creates drift. Vendoring exists because lockfiles still rely on the outside world being available and trustworthy, two things the incident proved cannot be taken for granted.
Both are responses to the same question I started with: how do we ensure our dependencies behave consistently across environments, builds, and time? The answer is layered, and the right layer depends on what you are actually protecting against.
Version drift and reproducibility across machines and pipelines → a committed lockfile handles that.
Registry availability, supply chain integrity, or offline builds → vendoring closes the gap lockfiles leave open.
The question worth sitting with is not which tool is better, but what failure mode you are designing against, and whether your current setup would hold if that failure showed up tomorrow.


