Understanding Bazel Output Directories
bazel-<workspace-name>
, bazel-out
, bazel-bin
, bazel-testlogs
, ~/.cache/bazel
et al.When a Bazel build is initiated, Bazel generates a sophisticated hierarchy of
out-of-source directories and artifacts (often under ~/.cache/bazel
on Linux)
and then symlinks to them from the project’s source tree via
bazel-<workspace-name>
, bazel-out
, bazel-bin
and bazel-testlogs
. These
aren’t just clutter—they’re integral to Bazel’s architectural design and
execution model. Understanding each directory’s role in a conventional Bazel
build is crucial to using Bazel effectively.
#Source Tree / Repository / Workspace
It is important to clarify the distinction between the terms “repository” and “workspace” as well as their relation to the project’s source tree, as they are often used interchangeably in Bazel.
Repository: A directory tree containing a set of related files, such as a project’s source code, a dependency’s source code, data files, or static assets. A directory is recognized as the root of a repository by the presence of a boundary marker file, such as
MODULE.bazel
or the legacyWORKSPACE
file. This marker establishes the root from which all internal repository paths are resolved.Main Repository / Source Tree: The directory tree, marked by an aforementioned boundary file, from which Bazel is currently being executed. Outside of Bazel nomenclature, this would simply be referred to as the project’s source tree.
External Repository: A repository that is not the main repository but is referenced as a dependency. External repositories are defined in
MODULE.bazel
(orWORKSPACE
) and are fetched and cached by Bazel. Their targets and files are referenced in BUILD files and Starlark code using the@repository_name
prefix (e.g.,@maven//:junit
,@rules_go//go:def.bzl
).Workspace: The complete build environment, which encompasses the main repository and the set of all defined external repositories (dependencies).
For further clarification, see Repositories, workspaces, packages, and targets.
Location:
Starlark:
- Labels: Reference targets in the main repository with
//package:target
or in external repositories with@repo//package:target
repository_name()
: Returns"@"
when called from the main repository, or"@repo_name"
when called from an external repositoryctx.label.workspace_name
: In rule implementations, returns the repository name (empty string for main repository, or the external repository name)
Usage Examples:
#Symlinks to Output Tree
Docs.
Bazel populates the source tree root (main repository/workspace root) with
several standard symbolic links. They serve as stable, human-readable pointers
to key locations within Bazel’s complex output tree
(
outputRoot
). While effective for
correctness and performance, the output tree paths are deeply nested, identified
by hashes, and reside outside the source tree, making them cumbersome for direct
developer interaction. The source tree symlinks bridge this gap, providing a
consistent and convenient developer experience by abstracting away the
implementation details of the output tree layout.
The symlinks include:
bazel-<workspace-name>
,
bazel-out
,
bazel-bin
, and
bazel-testlogs
.
#Output Tree (outputRoot
, ~/.cache/bazel
)
The following diagram is taken from the Bazel docs and may be slightly out of date.
The output tree, officially termed the outputRoot
and often found at
~/.cache/bazel
on Linux, serves as the centralized location for all of Bazel’s
persistent state on a machine, residing entirely outside any project’s source
tree. The key design goals of this directory are:
- Isolation: Prevent interference between different users on the same
machine, different workspaces, and different target configurations (e.g.,
fastbuild
,dbg
) within the same workspace. - Centralization: Centralize all build state under a dedicated root directory to avoid collisions with other development tools and make cleanup simple, whether for a single project or for all of a user’s Bazel activity.
- Accessibility: Provide stable, cached access to build artifacts.
- Clarity: Ensure each build’s context is unambiguous.
Location:
#outputUserRoot
(_bazel_$USER
)
Directly beneath the outputRoot
, Bazel creates the outputUserRoot
, a
directory typically named _bazel_$USER
. This directory serves as the primary
mechanism for isolating the build environments of different users on a shared
machine. By segregating all outputs, caches, logs, etc. on a per-user basis,
Bazel prevents permission errors, cache poisoning, and other state-related
conflicts that could arise in a multi-tenant development environment.
Location:
CLI Options:
--output_user_root=<path>
: Startup option that overrides the defaultoutputUserRoot
location. Useful for isolating user builds in non-standard environments.
Usage Examples:
#repository_cache
The repository_cache
is a content-addressable storage directory for external
dependency artifacts fetched by repository rules, such as
http_archive
or
http_file
. Located under the outputUserRoot
, this cache is
intentionally shared across all of a user’s workspaces, rather than being tied
to a specific project’s output_base
. This design promotes efficiency by
preventing redundant downloads of the same dependency version across different
projects.
When a rule defines a dependency with a SHA256 hash, Bazel downloads the
corresponding archive, verifies its integrity against the provided hash, and
then stores it in the repository_cache
. The artifact is typically stored using
its hash as its identifier. This mechanism is fundamental to ensuring build
hermeticity and reproducibility; it guarantees that any build requesting that
specific dependency hash will receive the exact same bits, insulated from
upstream changes or network issues. Subsequent build or fetch operations that
require the same artifact will retrieve it directly from this local, verified
cache, significantly accelerating dependency resolution.
Unlike most build outputs, this cache is not affected by the bazel clean
command. This design ensures that if workspace build outputs are cleaned,
subsequent builds will not incur the cost of re-downloading large dependency
archives. For situations requiring a complete purge of downloaded artifacts,
such as to resolve a corrupted entry or free disk space, the cache must be
removed manually.
Note: Not all repository rules populate the repository_cache
. Many
language-specific ecosystem rules, such as
Gazelle’s
go_repository
for Go dependency management, eschew Bazel’s native repository caching mechanism
and instead implement their own logic to fetch and manage source modules
directly.
For environments with restricted network access or for creating fully air-gapped builds, Bazel supports pre-populating this cache.
Location:
CLI Options:
--repository_cache=<path>
: Explicitly sets the location for the shared cache.--distdir=<path>
: Specifies a directory of tarballs that Bazel will consult before attempting network downloads.
Usage Examples:
Starlark:
Repository rules populate both the repository_cache
(with downloaded archives)
and the
external
directory (with extracted contents):
repository_rule()
: Function used in.bzl
files to define new types of external repositoriesrepository_ctx
: Context object passed to a repository rule’s implementation function, providing the API for interacting with the outside world:repository_ctx.download()
: Fetches a file from a URL and stores it in the repository cacherepository_ctx.download_and_extract()
: Downloads an archive to the cache and extracts itrepository_ctx.execute()
: Runs a command (often to process downloaded files)repository_ctx.path()
: Resolves a path to a file or dependencyrepository_ctx.file()
: Writes content to a file (often a generatedBUILD.bazel
file)
Usage Example:
Defining a custom repository rule:
#install_base
The install_base
directory serves as the unpacked, version-specific
installation root for the Bazel binary itself. While Bazel is distributed as a
single executable file, that binary is a self-extracting archive containing all
the necessary runtime components—such as the core Java server, built-in tools,
and standard repository definitions—required to bootstrap a build environment.
The install_base
is where these components are placed upon the first execution
of a specific Bazel version. If this directory becomes corrupted, it can be
safely deleted; Bazel will simply re-extract its contents on the next run.
The name of the install_base
directory is a hash derived from the Bazel
binary’s internal installation manifest. This hashing mechanism is critical for
version isolation. If a developer switches between Bazel 7.0.0 and 7.1.0 on the
same machine, each version will create its own distinct install_base
,
preventing conflicts between their respective runtime files. This design ensures
that a project’s build behavior remains consistent and tied to the specific
toolchain version being used, a key aspect of hermeticity.
Location:
#output_base
The output_base
directory is the per-workspace silo within the
outputUserRoot
where Bazel stores a workspace’s execution root
(
execRoot
), action cache
(
action cache
), external repositories
(
external
), server logs (
server
), and more. Its name
is a hash of the absolute path to the source tree root ensuring the same
workspace path always gets same output_base
and guaranteeing that two separate
checkouts of the same project will not interfere with each other’s build caches
or artifacts.
This directory is the primary target of the bazel clean
command. A standard
bazel clean
purges generated artifacts and cache entries within the
output_base
but preserves the overall directory structure. For a complete
reset of a workspace’s state, the bazel clean --expunge
command can be used to
remove the entire output_base
directory itself. Upon the next build
invocation, Bazel will create a new, empty output_base
, effectively starting
the project’s build state from scratch.
Location:
CLI Options:
--output_base=<path>
: This startup option overrides the default hash-based directory name. It forces all outputs for the current workspace into a specific, user-defined path, which is highly useful for CI systems that need predictable paths.bazel clean --expunge_async
: For very large repositories, this performs the expunge operation in the background, returning control to the terminal much faster.
Usage Examples:
#action_cache
The action_cache
is a persistent, on-disk key-value store that memoizes the
results of previously executed build actions. Its primary function is to enable
Bazel’s highly performant and correct incremental build model.
Before executing any action (e.g., compiling a source file, linking a binary), Bazel computes a unique cache key that consists of multiple components:
- actionKey: A hash of non-file data including the action’s command line arguments and mnemonic
- usedClientEnvKey: A hash of environment variables from the
--action_env
flag - digestKey: A hash of the action’s input and output files
When Bazel considers executing an action, it checks the action cache entry against its current in-memory version. If all digest fields match, the outputs in the output tree are valid and the action need not be rerun. This provides efficient incremental builds by skipping unnecessary work.
However, the action cache has limitations. It doesn’t retain entries across
certain state changes. For example, modifying a file, rebuilding, then reverting
the change will result in three builds rather than recognizing the third state
matches the first. This is because the action cache is relatively short-lived.
To address this limitation and enable cache sharing across machines, Bazel
supports remote caching via the --disk_cache
and --remote_cache
flags.
The cache itself is stored in an internal, opaque binary format and is not
intended for direct user inspection. Its contents can be dumped via
bazel dump --action_cache
and cleared via bazel clean
.
Location:
CLI Options:
--disk_cache=<path>
: Configures a separate, shareable on-disk cache. Unlike the internalaction_cache
, this cache can be shared by different workspaces on the same machine.--remote_cache=<url>
: Specifies the URL of a remote build cache, extending the action cache concept across a team and CI infrastructure.--noremote_upload_local_results
: When using a remote cache, prevents locally-run action results from being uploaded, useful for preventing a developer’s machine from “poisoning” a golden CI cache.
Related Tools:
Use aquery
(action graph query) to inspect the inputs that form an action’s
key:
Starlark:
The Starlark API doesn’t interact with the action cache directly, but it defines the inputs that create the cache key for every action:
ctx.actions.run()
andctx.actions.run_shell()
: Core functions for defining build actions. The action’s cache key is derived from:inputs
: List of inputFile
objects (content is part of the key)outputs
: List of declared outputFile
objectscommand
/arguments
: The command to executeexecutable
: The tool being run (contents are part of the key)env
: Environment variables passed to the actionexecution_requirements
: Tags that modify execution, like{"no-sandbox": "1"}
Usage Examples:
#execRoot
(bazel-<workspace-name>
)
The execution root, or execRoot
, is the base directory used by Bazel to create
sandboxed execution environments for build actions. Its fundamental purpose is
to create a consistent and hermetic build environment by assembling a complete,
self-contained “symlink forest”. This directory is what the
bazel-<workspace-name>
convenience symlink in the source tree root points to.
The execRoot
is constructed by symlinking several key components into a single
hierarchy:
- Main repository: The
_main
directory, which contains a complete representation of the main repository (hence the directory name_main
) - External repositories: The
_main/external
directory, which contains symlinks to the unpacked contents of all external repositories required by the build - Build outputs: The
_main/bazel-out
directory, which contains all generated build outputs (bazel-bin
,bazel-genfiles
,bazel-testlogs
)
Location:
CLI Options:
--subcommands
(or-s
): Prints the exact command line for each action executed, with paths shown relative to theexecRoot
. Invaluable for debugging rule behavior.--verbose_failures
: On action failure, prints the full command and environment variables, providing a complete snapshot of theexecRoot
context.
Starlark:
This is the single most important concept for a rule author. The entire rule
implementation function operates within the context of the execRoot
:
File.path
: For anyFile
object (input or output), the.path
attribute returns its path relative to theexecRoot
. This is the string you must use in an action’s command line.
Usage Examples:
In a rule implementation:
Make Variables:
$(execpath ...)
: Provides the robust way to get theexecRoot
-relative path for a dependency inside agenrule
$(location ...)
: Relative to thebin
directory
#output_path
(bazel-out
)
The output_path
, accessible via the bazel-out
convenience symlink in the
source tree root, is the designated directory within the execRoot
for all
artifacts generated during the build process. Its primary function is to isolate
build outputs based on their target configuration, preventing conflicts and
ensuring correctness when building for multiple platforms or with different
compilation flags.
Instead of placing all artifacts into a single flat directory, Bazel creates a
distinct subdirectory within output_path
for each unique combination of build
flags, toolchains, and platform settings. These configuration-specific
directories typically follow a [platform]-[cpu]-[compilation_mode]
pattern,
such as k8-fastbuild
for a non-optimized Linux build (the AMD K8 Hammer was
the first implementation of the AMD64 64-bit extension to the x86 instruction
set architecture) or darwin-arm64-opt
for an optimized build on Apple Silicon.
A special host
configuration directory is used for building tools that run on
the build machine itself as part of a larger build.
Location:
CLI Options:
--compilation_mode
(or-c
): Sets the build mode tofastbuild
(default),dbg
(debug), oropt
(optimized)--cpu
: Specifies the target CPU architecture for cross-compilation--config=<name>
: Activates a named configuration from.bazelrc
(e.g.,build:ci --copt=-g
). This is the standard way to create distinct, reusable configurations--platforms=<label>
and--host_platform=<label>
: Modern, platform-aware flags providing more precise control than--cpu
Usage Examples:
The bazel-bin
directory is a convenience symlink that provides a stable,
user-facing pointer to the location of final build artifacts. While bazel-out
is the root for all configuration-specific outputs, bazel-bin
points directly
into the bin/
subdirectory of the most recently used configuration. This
abstracts away the complexity of the configuration path (e.g., k8-fastbuild
),
offering a predictable location for developers and scripts to find executables,
libraries, and other deployable outputs.
This directory is populated with the primary outputs of binary and library rules
and the directory structure mirrors the package structure of the source tree.
For each executable target, Bazel also generates a corresponding .runfiles
directory, which is a symlink farm containing all the transitive runtime data
dependencies, shared libraries, configuration files, and other resources
required by the binary.
Internally, build rules refer to this location using the $(BINDIR)
make
variable. This variable resolves to the appropriate configuration-specific path,
ensuring that rule implementations can place their outputs in the correct
location without needing to be aware of the full output_path
structure. The
bazel-bin
symlink is simply the developer-friendly entrypoint to this
location.
Location:
Related Tools:
cquery
(configured query): Find exact paths to outputs
Usage Examples:
Make Variables:
$(location ...)
: Resolves to the full path withinbazel-bin
Starlark:
These directories are where a rule’s outputs are placed. The Starlark API provides mechanisms to declare files within these locations:
ctx.actions.declare_file(filename)
: Creates an outputFile
object. Bazel places this file in a package-relative path inside the configuration-specific output directory (e.g.,bazel-out/k8-fastbuild/bin/my/pkg/filename
)ctx.bin_dir
andctx.genfiles_dir
:File
objects representing the$(BINDIR)
and$(GENDIR)
directories. Their.path
attributes can construct paths for complex output layoutsDefaultInfo(files=...)
: Signals primary outputs by returning this provider with adepset
ofFile
objects
Usage Examples:
Declaring and creating outputs in a rule implementation:
The bazel-genfiles
convenience symlink is a legacy component maintained
primarily for backward compatibility. In modern versions of Bazel (post-0.25),
its target is identical to that of bazel-bin
, and for all practical purposes,
the two are interchangeable.
Historically, Bazel employed a bifurcated output strategy within each
configuration directory. The bin/
subdirectory was reserved for compiled
artifacts like executables and libraries, while a parallel genfiles/
directory
was designated for intermediate outputs, particularly generated source code
(e.g., from protoc
or custom genrule
actions). Rules would use $(BINDIR)
to refer to the former and $(GENDIR)
for the latter.
To simplify the output tree model and reduce ambiguity for rule authors, this
distinction was removed. All build outputs—both compiled binaries and generated
sources—are now consolidated into the bin/
directory. This creates a single,
predictable location for all artifacts produced by a given target.
Location:
The bazel-testlogs
symlink points to the canonical location for all artifacts
generated by test executions. It serves as the primary interface for developers
and continuous integration systems to access detailed results, logs, and other
outputs for post-test analysis. Similar to bazel-bin
, this is a dynamic
symlink whose target is updated after each bazel test
command to point to the
testlogs
subdirectory within the relevant configuration’s output path (e.g.,
bazel-out/k8-fastbuild/testlogs
).
The directory structure within bazel-testlogs
mirrors the package hierarchy of
the source workspace, making it easy to locate the results for a specific test
target. For each test target, this directory contains several key files:
test.log
: Captures the raw standard output and standard error streams from the test process, making it the primary resource for debugging failures.test.xml
: A structured report in JUnit XML format, designed for consumption by CI systems and other test reporting tools to parse test suite results, timings, and failure statuses.test.cache_status
: A simple text file indicating whether the test result was a cache hit (local
orremote
) or if it was executed (not_cached
).
Beyond these standard files, Bazel provides a test.outputs
subdirectory for
tests that produce undeclared output artifacts, such as screenshots on a UI test
failure or performance data dumps. For tests configured to run with flaky
attempt retries (via --flaky_test_attempts
), this directory will also contain
logs for each individual attempt.
Location:
CLI Options:
--test_output=all | errors | streamed
: Controls test log verbosity.streamed
prints in real-time,all
saves everything totest.log
,errors
only on failure--cache_test_results=no
: Forces tests to re-run and generate fresh logs, even if inputs haven’t changed--test_summary=detailed | short | terse
: Controls the format of the test summary printed to console
Related Tools:
query
: Find test targets whose logs would appear here
Usage Examples:
Notes:
- The
test_env
attribute on test rules (likesh_test
) allows passing environment variables into the test execution environment, whose effects can be observed intest.log
Starlark:
A rule author doesn’t directly write to this directory. Instead, they create a
test rule that, when executed by bazel test
, has its outputs captured and
placed here by the test runner:
DefaultInfo(executable=...)
: For a rule to be a test (e.g.,*_test
), it must provide an executable file in itsDefaultInfo
providertesting.TestEnvironment(env)
: Provider used to pass environment variables to the test runner
Usage Examples:
Creating a custom test rule:
#external
The external
directory, located directly within the output_base
, serves as
the staging area for the unpacked contents of all external repositories required
by the workspace. It is the concrete on-disk representation of the third-party
dependencies that are conceptually referenced in BUILD
files using the
@repository_name//path/to:target
syntax.
This directory is populated by Bazel’s repository rules, which are defined in
the MODULE.bazel
or legacy WORKSPACE
file. When Bazel resolves a dependency
managed by a rule like
http_archive
or
git_repository
, it fetches
the specified artifact—often storing the compressed archive in the shared
repository_cache
—and then unpacks it into a subdirectory here. The name of
this subdirectory directly corresponds to the repository’s declared name (e.g.,
com_google_protobuf
for the repository named @com_google_protobuf
).
In addition to user-defined dependencies, this directory also houses special
repositories that are essential for Bazel’s own operation. These include
@bazel_tools
, which contains built-in build rules and tools, and various
auto-configuration repositories like @local_config_cc
that are generated to
detect and configure the host system’s toolchains.
A critical distinction exists between this directory and the repository_cache
.
The repository_cache
stores the immutable, downloaded archives, while the
external
directory contains the mutable, extracted source trees that build
actions will actually consume via symlinks in the execRoot
. Consequently, this
directory persists through a bazel clean
command (to avoid the cost of
re-extracting archives) but is removed by bazel clean --expunge
, which forces
a complete refetch and re-extraction of all external dependencies.
Location:
CLI Options:
--override_repository=<repo_name>=<local_path>
: Replaces an external repository with a local directory. Essential for developing a dependency and its consumer in parallel without pushing changes.
Usage Examples:
Related Tools:
query
: List all defined external dependencies
Starlark:
The contents of this directory are created by repository rules, which first
download archives to the repository_cache
and then extract them here. See the
repository_cache section for details on the Starlark API
for repository rules.
#command_log
The command.log
file, located directly within the output_base
, serves as a
direct transcript of the standard output and standard error streams from the
most recent Bazel command invocation. This file is intentionally ephemeral; its
content is completely overwritten each time a new Bazel command (such as
build
, test
, or query
) is executed within the workspace.
Its primary utility is for post-hoc analysis of the last command that was run,
which is particularly useful in non-interactive environments like CI/CD
pipelines or for examining the output of a command after the terminal buffer has
been cleared. The log captures the same information that is printed to the
console during a run: progress indicators, INFO
messages, warnings, error
diagnostics, and the final build summary.
It is important to distinguish this log from other logging mechanisms within
Bazel. The command.log
provides a high-level summary of the overall
invocation, not the granular, per-action output. It is also distinct from the
structured test results in bazel-testlogs
and the persistent Bazel server logs
found in the
server/
directory. Because it resides in the
output_base
, the command.log
survives a bazel clean
but is deleted by
bazel clean --expunge
.
Location:
#command.profile.gz
The command.profile.gz
file is a gzipped, JSON-formatted trace file containing
detailed performance metrics from the most recent Bazel command. It is the
primary mechanism for diagnosing and optimizing build performance. This file is
automatically generated by default for commands like build
and test
and is
overwritten with each new invocation. It captures a granular log of trace events
throughout the build process, including:
- Phase Timings: Time spent in distinct phases like loading, analysis, and execution.
- Critical Path Analysis: The sequence of dependent actions that determines the overall build duration.
- Action-Level Metrics: The start time, duration, and resource consumption of every individual build action.
- System Telemetry: Information on memory usage, garbage collection, and CPU utilization.
Location:
Related Tools:
bazel analyze-profile
: Primary tool for consuming profile data
Usage Examples:
More analysis tools can be found in the JSON Trace Profile docs.
#sandbox
The sandbox
directory, located within the output_base
, is where Bazel
creates the ephemeral, isolated filesystem environments used to execute
individual build actions. Its existence is fundamental to Bazel’s core principle
of hermeticity, ensuring that build actions are shielded from the host system
and can only access their explicitly declared inputs. This guarantees
reproducibility and correctness by preventing undeclared dependencies or
variations in the host environment from influencing the build result.
For each action it executes, Bazel dynamically creates a unique temporary
subdirectory within the sandbox
directory. Inside this subdirectory, it
constructs a minimal, self-contained execution root by symlinking in only the
action’s declared input files, required tools, and necessary output directories.
The build command (e.g., a compiler invocation) is then executed with this
temporary directory as its root, effectively blinding it to any other files on
the filesystem.
Under normal operation, this process is entirely transparent. Upon successful
completion of an action, Bazel moves the generated artifacts from the sandbox to
their final destination within bazel-out
and immediately deletes the temporary
sandbox directory. As a result, the top-level sandbox
directory is typically
empty after a build completes.
However, it becomes a critical debugging tool when an action fails. By running a
build with the --sandbox_debug
flag, developers can instruct Bazel to preserve
the sandbox environment for any failed action. This allows for post-mortem
analysis of the isolated filesystem, making it possible to verify which files
were present and debug issues related to missing inputs or incorrect paths.
Location:
CLI Options:
--sandbox_writable_path=<path>
: Allows sandboxed actions to write to specified host filesystem paths.--sandbox_block_path=<path>
: Explicitly prevents sandboxed actions from accessing given paths.--subcommands
(or-s
): Prints the exact command being run inside the sandbox.
Usage Examples:
#server_log
The server_log
is the primary diagnostic log for the persistent Bazel server
process itself. Unlike command.log
, which captures the high-level output of a
specific build command, the server_log
records the low-level internal
operations of the underlying JVM that hosts the server. Its contents are
intended primarily for debugging the Bazel application, not for analyzing a
user’s build.
The log file contains a verbose stream of events related to the server’s lifecycle and health. This includes server startup arguments, process ID, memory management details such as garbage collection cycles and heap usage, internal data structure operations like Skyframe graph evaluations, and RPC communications between the Bazel client and server. It is also the destination for any unhandled exceptions or stack traces generated within the server process.
A new log file is generated each time the Bazel server process is started, and its filename is structured to be unique, typically incorporating the hostname, username, timestamp, and server process ID.
Location:
#server
The server
directory, located within the output_base
, contains the state and
communication files for the persistent Bazel server process. Of these files,
jvm.out
is the most useful to users as it captures the raw standard output and
error streams from the server’s JVM.
Location:
Recent posts
Subscribe, unless you hate fun
Get the latest posts, updates, and half-decent advice — straight to your inbox. No spam. Just vibes.