Bazel Book

Output Root

After your first bazel build, new entries appear at the workspace root — not source files, but convenience symlinks pointing into Bazel's output directory hierarchy1. Bazel never writes build artifacts into the source tree2. All outputs go to a separate directory, and the symlinks provide quick access to find them.

The Convenience Symlinks

Four symlinks appear alongside MODULE.bazel (0.1.2 Repository Root) and your BUILD.bazel files1:

my-project/
├── MODULE.bazel
├── BUILD.bazel
├── app/
│   ├── BUILD.bazel
│   └── main.cc
├── bazel-bin → ...            ← compiled outputs
├── bazel-my-project → ...     ← execution root
├── bazel-out → ...            ← raw output root
└── bazel-testlogs → ...       ← test logs
SymlinkContents
bazel-bin/Compiled binaries, libraries, and generated files for the current build configuration.
bazel-my-project/The execution root — the working directory where build actions run. Named after the project directory.
bazel-testlogs/Test logs and results (test.log, test.xml) for the current configuration.
bazel-out/The complete output root for all configurations. bazel-bin is a shortcut into one specific configuration's bin/ directory inside bazel-out.

These symlinks are for your convenience only — Bazel does not use them internally1. They point into the output base, a per-workspace directory that Bazel manages outside your source tree.

Path Mirroring

Output paths mirror the package structure (0.1.3 Package): the package part of the label becomes the subdirectory under Bazel's output trees. Most targets have one main output location. Tests are the common exception: Bazel builds the test program under bazel-bin/, then writes execution artifacts such as logs and XML under bazel-testlogs/1.

The diagram below shows one runnable example, reproduced by the output-path-mirroring snippet. The sample files are representative, not exhaustive: real output directories often include helper files such as params files or test metadata.

Label
Outputs
//app:server
C++ binary
Build artifact
bazel-bin/app/server
//lib/util:helpers
Java library
Build artifact
bazel-bin/lib/util/libhelpers.jar
//tests/api:api_test
C++ test
Build artifact
bazel-bin/tests/api/api_test
Test run artifacts dir
bazel-testlogs/tests/api/api_test/
Sample files
test.log · test.xml

That is why tests break the simple "one label -> one final file" mental model: one test target can have a built test executable in bazel-bin/ and a separate per-target artifact directory in bazel-testlogs/1.

Configurations Inside bazel-out

Inside bazel-out/, Bazel creates a subdirectory for each build configuration — a combination of target platform, compilation mode, and other settings1:

bazel-out/
├── k8-fastbuild/
│   ├── bin/                ← compiled outputs (BINDIR)
│   └── testlogs/           ← test results
├── k8-opt/
│   ├── bin/
│   └── testlogs/
└── _tmp/
    └── actions/            ← stdout/stderr from build actions

The bazel-bin and bazel-testlogs symlinks point to one configuration at a time: the configuration of your most recent top-level build or test. Change flags such as -c opt, build for a different platform, or switch to a top-level target with a different configuration, and those symlinks may point somewhere else. Older outputs still remain in bazel-out/.

If you need the full picture, look in bazel-out/. It keeps one subtree per configuration, while bazel-bin and bazel-testlogs are just shortcuts into one of them.

extra

Why the Shortcut Can Move

bazel-out/ keeps separate subtrees for separate configurations such as k8-fastbuild/ and k8-opt/. bazel-bin can point to only one of those bin/ directories at a time, and bazel-testlogs works the same way for testlogs/.

That means two things in practice:

  • Rebuilding with different flags can move the shortcut to a different subtree.
  • Older outputs can still exist on disk under another bazel-out/<config>/... path even after the shortcut has moved.

So if an expected artifact is "missing" from today's bazel-bin, the usual explanation is not that Bazel deleted it, but that you are looking through the wrong configuration shortcut. Later, 3.3 Configurable Builds & Platform Basics explains the deeper mechanisms behind those configuration changes.

Why Outputs Live Outside the Source Tree

Bazel stores all outputs in a directory derived from an MD5 hash of the workspace root path, located under ~/.cache/bazel/ on Linux or ~/Library/Caches/bazel/ on macOS3. This isolation ensures that3:

  • Multiple users on a shared machine don't collide — each user gets a separate output root.
  • Multiple workspaces each get their own output base, even for the same project checked out in different paths.
  • Multiple configurations coexist under bazel-out/ without overwriting each other.

You can inspect these paths with bazel info2:

$ bazel info output_base
/home/user/.cache/bazel/_bazel_user/7ffd56a6e4cb724ea575aba15733d113

bazel clean removes the output path and action cache. bazel clean --expunge removes the entire output base, including server state and fetched external dependencies3.

Gitignoring Output Directories

The convenience symlinks should be excluded from version control:

/bazel-*

This catches all four convenience symlinks: bazel-bin, bazel-out, bazel-testlogs, and bazel-<project-name>1.

key takeaway

Bazel never writes into your source tree. Build outputs live in a separate directory hierarchy, with convenience symlinks (bazel-bin, bazel-out, bazel-testlogs, bazel-<project-name>) placed at the workspace root for easy access. Target labels predict where to look in that tree; the target kind tells you what files are likely to be there, and tests are the common case where Bazel gives you a per-target directory instead of one file.

extra

The Full Output Directory Tree

Beneath the system cache directory, Bazel maintains a layered hierarchy designed for complete isolation between users, Bazel installations, and workspaces3:

~/.cache/bazel/                               ← outputRoot
└── _bazel_$USER/                             ← outputUserRoot
    ├── install/
    │   └── <md5-of-bazel-install>/           ← installBase
    └── <md5-of-workspace-path>/              ← outputBase
        ├── action_cache/                     ← persistent action cache
        ├── external/                         ← fetched external repos
        ├── server/                           ← Bazel server state
        └── execroot/
            └── _main/                        ← execRoot
                └── bazel-out/                ← outputPath

The install base is hashed from the Bazel installation manifest, allowing multiple Bazel versions to coexist.

The execution root (execroot/_main/) is the working directory for all build actions4. It contains a symlink forest of input files alongside the bazel-out directory for outputs. This structure is the foundation for sandboxing (2.3.2 Sandboxing) and remote execution (6.2 Remote Build Execution (RBE)).

Check your understanding

1.Where does Bazel write build artifacts?

2.What does bazel-bin/ contain?

3.Why might an expected artifact appear to be missing from bazel-bin?

Answer all questions to check

Footnotes

  1. Output Directory Layout — Convenience symlinks, layout diagram, and configuration directory structure 1 2 3 4 5 6 7

  2. Bazel 101 Training (Part 6): Create a repositorybazel info for inspecting output paths; Bazel never writes to the source tree 1 2

  3. Output Directory Layout — outputRoot, outputUserRoot, outputBase hierarchy; MD5 hashing; bazel clean behavior 1 2 3 4

  4. Bazel Glossary — Execution root: working directory for local actions with symlinked inputs