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.

key takeaway

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