0.1.2 Repository Root
With Bazelisk and .bazelversion handling the build tool itself (0.1.1 Bazelisk & .bazelversion), the next file to look for is the one that marks where the project begins. Every Bazel project needs a root, the single directory that anchors the entire build. Bazel finds this root by looking for a boundary marker file1. Without one, Bazel doesn't know where your project begins, and no command will work.
The Boundary Marker Files
Three file types can serve as a repository boundary marker2:
| File | Purpose |
|---|---|
MODULE.bazel | The default since Bazel 7.0. Marks the root and declares dependencies. |
REPO.bazel | Also marks the root. Optionally sets repo-wide defaults. |
WORKSPACE / WORKSPACE.bazel | Legacy (pre-Bazel 9). Removed in Bazel 93. |
Multiple marker files can coexist in the same directory2. In practice, a modern project usually has a MODULE.bazel at the root and may also have a REPO.bazel beside it.
A typical project root looks like this:
my-project/
├── .bazelversion
├── .git/
├── MODULE.bazel
├── REPO.bazel (optional)
├── BUILD.bazel
├── app/
│ ├── BUILD.bazel
│ └── main.cc
└── lib/
├── BUILD.bazel
└── utils.cc
The .bazelversion file from the previous article pins the Bazel release, a
boundary marker such as MODULE.bazel establishes the repository root, and
BUILD.bazel files create the packages covered next in 0.1.3 Package.
MODULE.bazel
This file serves two purposes4:
- Root marker — tells Bazel where the repository begins.
- Dependency declaration — lists external modules the project depends on.
A minimal MODULE.bazel can be empty. Its presence is enough to mark the root. A typical one looks like this:
module(name = "my-project", version = "1.0")
bazel_dep(name = "rules_cc", version = "0.2.17")
bazel_dep(name = "platforms", version = "1.0.0")
How bazel_dep() works, version resolution, and the Bazel Central Registry are covered in 3.1 Dependency Management. At this stage, MODULE.bazel sits at the root and anchors everything else.
REPO.bazel
REPO.bazel is also a repository boundary file2. Like MODULE.bazel, it can be empty and still mark the root. Its extra job is to set repo-wide defaults for all build targets. For example, applying a common license to all targets:
repo(
default_package_metadata = ["//:my_license"],
)
It also provides ignore_directories() to exclude directories from Bazel's view (e.g., node_modules)5. This is the modern replacement for .bazelignore, covered in 3.2.2 .bazelignore.
In modern Bzlmod-based projects, REPO.bazel usually complements MODULE.bazel rather than replacing it. MODULE.bazel carries module and dependency declarations. REPO.bazel carries repo-wide defaults. Either file counts as a boundary marker.
WORKSPACE (Removed)
Before Bzlmod, the WORKSPACE file was Bazel's only root marker and dependency management mechanism. It required all transitive dependencies to be declared manually, leading to fragile and verbose configurations2. It was replaced by MODULE.bazel, disabled by default in Bazel 8, and fully removed in Bazel 93. A variant named WORKSPACE.bazel also existed, for the same case-sensitivity reasons that BUILD.bazel is preferred over BUILD (covered in 0.1.3 Package).
If you encounter a WORKSPACE file in an existing project, it may not have migrated to Bzlmod yet. See M1 WORKSPACE → Bzlmod.
One Bazel Repo ≈ One Git Repo
A Bazel repository and a Git repository are typically one-to-one, with MODULE.bazel at the same level as .git/5. Nesting multiple Bazel repositories inside one Git repository, or placing a Bazel repository in a subdirectory, is possible but unusual and generally discouraged4. Bazel calls this directory tree a repository. A workspace is the logical build environment formed by the main repository and the external repositories available to the build. It is neither a directory that contains those repositories nor the legacy WORKSPACE file. Bazel resolves module dependencies and makes their repositories available. Module extensions can create more repositories. You explicitly import selected ones with use_repo(). The apparent name used to address an external repository can depend on the repository mapping of the repository containing the label. This distinction becomes relevant when working with external dependencies and labels (0.2.1 Label Anatomy).
main code tree
external code tree
external code tree
In modern projects, MODULE.bazel is usually the first root file you look for. A REPO.bazel may sit beside it and also counts as a boundary marker. Once Bazel finds that root, all packages, labels, and build paths are resolved relative to it.
Check your understanding · 2 questions
1.Match each boundary marker file to its primary role in a modern Bazel project:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
2.True or false: repository boundary marker behavior.
Choose True or False for each sentence
Footnotes
-
Repositories, workspaces, packages, and targets — Authoritative definitions of repositories, workspaces, and their relationship ↩
-
External dependencies overview — Boundary marker files (MODULE.bazel, REPO.bazel, WORKSPACE), REPO.bazel syntax, and legacy WORKSPACE shortcomings ↩1 ↩2 ↩3 ↩4
-
Bzlmod Migration Guide — WORKSPACE removal timeline: MODULE.bazel default since 7.0, WORKSPACE disabled in 8, removed in 9 ↩1 ↩2
-
Bazel Training 101 (Part 7): Bazel Modules (MODULE.bazel) — MODULE.bazel as root marker and dependency description, one module per repository ↩1 ↩2
-
Bazel 101 Training (Part 6): Create a repository — Repository structure, REPO.bazel for ignoring directories, Bazel-Git repo correspondence ↩1 ↩2