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
| Symlink | Contents |
|---|---|
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.
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.
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.
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.
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)).
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?
Footnotes
-
Output Directory Layout — Convenience symlinks, layout diagram, and configuration directory structure ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Bazel 101 Training (Part 6): Create a repository —
bazel infofor inspecting output paths; Bazel never writes to the source tree ↩1 ↩2 -
Output Directory Layout — outputRoot, outputUserRoot, outputBase hierarchy; MD5 hashing;
bazel cleanbehavior ↩1 ↩2 ↩3 ↩4 -
Bazel Glossary — Execution root: working directory for local actions with symlinked inputs ↩