Bazel Book

Filesystem Hierarchy

At the start of Level 0, Bazel shows up less as a command set and more as repository structure. A few files and directories have special meaning: MODULE.bazel or REPO.bazel mark where the repository begins, BUILD.bazel files divide the tree into packages, and build outputs appear outside the source tree with convenience symlinks at the top level. If the project uses Bazelisk, .bazelversion also fixes which Bazel release interprets that structure. This section is about learning to see the repository the way Bazel sees it.

In many tools, directories mostly help humans stay organized. In Bazel, some directories and marker files are part of the build model itself. Adding one BUILD.bazel can change file ownership, label syntax, glob() behavior, and visibility boundaries. That is why Level 0 starts with geography: until the tree stops looking like "just folders" and starts looking like repository root plus packages plus generated outputs, the next sections on 0.2 Language of Labels and 0.3 Starlark Syntax Basics feel more arbitrary than they really are.

Four Boundaries Matter First

The section moves through four different boundaries, each one narrowing Bazel's view of the world.

  • 0.1.1 Bazelisk & .bazelversion starts even before the repository model itself. It explains how the project chooses which Bazel binary runs the repo. Version pinning is not unique to Bazel, but Bazelisk plus .bazelversion is the mechanism this ecosystem commonly uses.
  • 0.1.2 Repository Root introduces the outer border of the project. Bazel needs one directory that counts as the root of the repository, and marker files such as MODULE.bazel or REPO.bazel establish that border.
  • 0.1.3 Package then shows how the inside of the repository gets subdivided. Packages are not just a way to keep folders tidy. They are the unit of ownership and encapsulation, and a BUILD.bazel file is what creates that boundary.
  • 0.1.4 Output Root closes the loop by separating source world from generated world. Bazel's outputs live outside the source tree, with convenience symlinks at the root to help humans navigate them.

These are different ideas, but they stack cleanly. First the Bazel version is fixed. Then Bazel knows where the repository begins. Then it knows which package owns which files. Then it knows where generated artifacts belong. Once those four borders are visible, a lot of Bazel stops feeling magical.

The Real Mental Shift

The main shift in this section is that the filesystem is no longer passive background scenery. In a Bazel repository, structure is meaning.

If a directory has no BUILD.bazel, it is just part of some ancestor package. Add one file, and now that directory becomes its own package with its own labels, its own visibility boundary, and its own relationship to recursive globs. If a file lives under bazel-bin, it is not "part of the repo" in the same way as a file under app/ or lib/; it is generated state, tied to one build configuration and safe to throw away. If MODULE.bazel moves, Bazel's notion of the whole repository moves with it.

That is why this section comes before commands, rules, and even most syntax. Bazel does not begin by asking "what should I compile?" It begins by asking "what repository am I in, which package owns this path, and is this source or output?" Those are filesystem questions before they become graph questions.

Why Beginners Get Turned Around Here

A lot of early confusion comes from mixing categories that Bazel keeps separate.

One common mistake is to read MODULE.bazel only as a dependency file and miss that it is also a root marker. Another is to assume that every directory is a package because it looks important in the source tree. If the repository uses Bazelisk, it is also easy to treat .bazelversion as just another root file instead of part of how the project selects its Bazel version. And once Bazel produces outputs, many newcomers instinctively browse bazel-bin/ as if it were part of the source layout rather than a generated mirror.

The section is meant to untangle those categories. It gives each one a place in the model so that paths stop blurring together. After that, a path can answer richer questions: is this path inside the repository or outside it? Does this directory define a package or merely belong to one? Is this file something I edit, or something Bazel synthesized for one configuration? Those are the questions that make later topics feel concrete instead of ceremonial.

How To Read The Section

The articles are ordered from the edge of the experience inward.

If you are onboarding to a real repository and want the fastest orientation, the conceptual core is 0.1.2 Repository Root plus 0.1.3 Package. Those two articles explain where the project begins and how the tree is partitioned. 0.1.1 Bazelisk & .bazelversion matters the moment you actually want to run Bazel instead of just reading files, because it explains why the command itself is version-aware. 0.1.4 Output Root becomes more intuitive after you have already seen Bazel generate something and wondered why it did not appear next to the sources.

There is also a deliberate narrative in reading them in order. The sequence moves from "which Bazel am I using?" to "where is the repo?" to "which package owns this file?" to "where did Bazel put the result?" That is a small story, but it is the right one: from entering the project, to orienting yourself inside it, to understanding where Bazel's own world diverges from yours.

key takeaway

This section teaches Bazel's geography before Bazel's grammar. Repository markers, BUILD.bazel files, package boundaries, and output symlinks are not incidental files around the build. They are the structures that let Bazel decide where the project starts, which package owns each file, and where generated artifacts belong. When the project uses Bazelisk, .bazelversion also fixes which Bazel release interprets that repository. Once those boundaries are clear, labels and BUILD syntax have something solid to attach to.