3.1 Dependency Management
A maintainer's dependency edit often starts as one tidy line in MODULE.bazel:
bazel_dep(name = "rules_python", version = "1.6.3"). Bazel still has to
resolve a module graph, map apparent @repo names to real repositories,
evaluate extension tags, fetch source archives, trust registry metadata, and
keep the result reproducible for developers and CI.
At Level 1, dependencies looked like labels in deps. At Level 2, they became
graph edges, phases, and cache inputs. A maintainer also has to ask which part of
the dependency system a change affects and who will have to live with it.
The Dependency Stack Has Layers
This section is ordered from the everyday surface down to the operational layers that make the surface reliable.
The first cluster is the Bzlmod working model. 3.1.1 Bzlmod (MODULE.bazel) explains how
MODULE.bazel declares direct module dependencies, how Bazel resolves the graph,
and why root-only overrides are the maintainer escape hatch. 3.1.2 Using Extensions
then covers the bridge to package managers that are not native Bazel modules:
use_extension(), extension tags, and use_repo() turn structured module input
into generated repositories. 3.1.3 Repo Mapping closes that core model by
explaining why @name is an apparent name in a particular context, not a stable
filesystem path or universal repository identity.
The second cluster is brownfield reality. 3.1.4 Legacy WORKSPACE Model teaches you
to recognize the older WORKSPACE model because old docs, old repos, and
mixed-mode migrations still contain it. 3.1.5 Eager Fetch Anti-pattern shows one of the
most common performance traps from that world and from careless BUILD-file
structure: an external load() can make Bazel fetch or prepare dependencies
while it is still loading packages, before the requested action has any reason to
need them.
The third cluster is dependency infrastructure. 3.1.6 BCR Infrastructure
separates registry metadata from source archives and explains why mirrors,
downloader policy, lockfiles, and repository caches matter. 3.1.7 Registry Immutability
adds the publication rule: registry entries behave like immutable metadata, not
like a development scratchpad. 3.1.8 Registry Structure & Custom Registries gives the file-format
view of BCR and private registries, including how --registry builds a registry
chain. 3.1.9 Offline / Air-Gapped Builds and 3.1.10 Cloud Storage Dependencies are the specialist
operational branches: one for no-network or portable dependency sources, the
other for cases where private object storage is the dependency source of truth.
The final article, 3.1.11 MODULE.bazel Management at Scale, is about scale. Once a root module has many direct dependencies, many overrides, and many teams changing it, dependency management stops being only a Bazel syntax problem. It becomes an ownership and review problem around the one root module that integrates the final graph.
Diagnose The Dependency Layer
Bzlmod resolves names and versions, but a maintainer also operates graph visibility, repository mapping, fetch laziness, registry availability, archive stability, cache warming, and review ownership.
That does not mean every project needs a private registry, a vendored dependency
tree, or enterprise automation on day one. It means the layers should not be
confused. If a label cannot see @maven, the problem may be use_repo() or repo
mapping, not the registry. If a tiny target downloads a large package ecosystem,
the problem may be an eager load(), not lack of caching. If CI fails during a
registry outage, the problem may be metadata reachability or archive mirroring,
not the bazel_dep() declaration. If every dependency patch creates merge
conflicts, the next improvement may be include() and reviewer routing, not a
cleverer version string.
Why Dependency Work Sits Beside The Target Graph
bazel_dep() is not a deps edge between two build targets. Dependency
management sits partly outside the target graph: a module extension can aggregate
tags from many modules before creating repositories. A registry MODULE.bazel
can be the resolution source of truth even if the downloaded archive contains a
different file. A repository can have one canonical name and many apparent names.
A cache can hide network cost without removing the dependency edge that caused
the cost.
These behaviors follow from one design choice: Bazel resolves external dependencies before analyzing the build graph, then exposes them to BUILD files as repositories with precise visibility. Once you see that boundary, the section becomes less like a bag of features and more like a chain: declare modules, resolve the graph, create repositories, map names, fetch sources, and operate the infrastructure around those steps.
Choose The Stack You Operate
If you are maintaining a normal modern repo, read 3.1.1 Bzlmod (MODULE.bazel),
3.1.2 Using Extensions, and 3.1.3 Repo Mapping first and read them in order.
Together they explain the path from MODULE.bazel to usable @repo//... labels.
Do not skip repo mapping if your immediate task is "just add a dependency". Most
confusing Bzlmod errors eventually involve which repository is visible from which
context.
If you are migrating or reading old documentation, jump next to
3.1.4 Legacy WORKSPACE Model and 3.1.5 Eager Fetch Anti-pattern. They are the antidote to
WORKSPACE-era habits: manually recreating transitive repos, relying on ordering,
or putting generated external .bzl loads in places that make unrelated targets
pay for them.
If your concern is production reliability, read the infrastructure cluster as a unit: 3.1.6 BCR Infrastructure, 3.1.7 Registry Immutability, and 3.1.8 Registry Structure & Custom Registries. Then use 3.1.9 Offline / Air-Gapped Builds when the problem is no-network operation, and 3.1.10 Cloud Storage Dependencies when dependency artifacts really live in private object storage. Save 3.1.11 MODULE.bazel Management at Scale for the point where dependency edits are colliding across teams or the root module is becoming its own review bottleneck.
Dependency management turns declared module intent into resolved, named, fetched, cached, and reviewable repositories. Diagnose resolution, naming, fetching, infrastructure, and ownership as separate layers.
Sections in this chapter · 12
Maintainer workflow: declare modules, resolve versions, lock the graph, and apply root-only overrides for local dev and patching.
Integrating language-specific package managers through the extension mechanism.
How Bazel translates between canonical and apparent repository names across modules.
The pre-Bzlmod dependency system still found in brownfield projects and older documentation.
How load() statements can force unnecessary repository fetches and how to detect them.
Mirroring, downloader config, and archive stability concerns for the Bazel Central Registry.
Registry files are cached in-memory and must never change after initial fetch.
BCR module format and hosting internal registries for proprietary modules.
Vendoring and repository caching strategies for building without network access.
Fetching dependency artifacts from cloud object storage through Bazel-aware repository logic.
Challenges and workarounds when MODULE.bazel grows to thousands of lines with hundreds of overrides.
Coordinating Bzlmod state with ecosystem manifests and generated resolution files.