Bazel
Bazel is the open-source version of Google's internal build system, Blaze. It builds and tests software in many languages — C++, Java, Go, Python, TypeScript, Rust, and more — from one dependency graph, with a strong guarantee: given the same inputs, a build step produces the same outputs. Because every action declares its inputs exactly, Bazel can cache results, skip anything unaffected by a change, and distribute work across remote machines. In a large monorepo, that turns hour-long builds into minutes.
That power has a price. Bazel asks you to declare dependencies precisely, adopt its rules for each language, and sometimes fight ecosystems built around their own tools. It pays off most for large, multi-language codebases with many contributors; small single-language projects rarely need it.
TL;DR
- Code is organized into packages (directories with
BUILD.bazelfiles) containing targets, addressed by labels like//services/checkout:server. - Targets are defined with rules (
cc_library,go_binary,py_test) written in Starlark. - External dependencies are managed with Bzlmod (
MODULE.bazel). - Builds are hermetic and sandboxed: actions see only declared inputs.
- Caching (local, disk, remote) and remote execution make builds fast and shareable across machines and CI.
bazel queryanswers "what depends on this?", enabling CI to build and test only affected targets.
Quick Example
Declare dependencies in MODULE.bazel and targets in BUILD.bazel:
visibility makes dependency rules enforceable: only targets under //services may depend on the pricing library.
Core Concepts
Packages, Targets, and Labels
Rules and Starlark
Rules define how to turn inputs into outputs. Language rule sets — rules_cc, rules_java, rules_go, rules_python, rules_js, rules_rust, rules_oci for container images — provide them. Custom rules and macros are written in Starlark, a deterministic, Python-like language. Tools such as Gazelle generate and update BUILD files for Go, Protocol Buffers, and other languages.
Bzlmod
Bzlmod manages external dependencies through MODULE.bazel and the Bazel Central Registry, with version resolution across the dependency graph. It replaces the older WORKSPACE mechanism, which recent Bazel releases disable by default.
Hermeticity and Sandboxing
Each build action — compiling a file, linking, running a test — declares its inputs and outputs. Bazel runs actions in a sandbox that exposes only declared inputs, so undeclared dependencies fail loudly instead of working by accident on one machine. Toolchains (compilers, SDKs, interpreters) are also declared, so builds do not depend on what happens to be installed.
Caching and Remote Execution
Remote caching and execution use the open Remote Execution API, implemented by services such as BuildBuddy, EngFlow, and NativeLink, and by self-hosted options such as Buildbarn.
Bazel in CI
- Use the remote cache so CI only rebuilds what changed since any previous build anywhere.
- Test affected targets: compute changed files, find dependent targets with
bazel query(or tools such as bazel-diff and target-determinator), and test those. - Separate configs in
.bazelrcfor local, CI, and release builds. - Pin Bazel with
.bazelversionand the Bazelisk launcher, so everyone uses the same version. - Stamp release builds with version and commit information only where needed, since stamping reduces cache hits.
When Bazel Fits
Alternatives: Buck2 (Meta, similar model), Pants (strong Python support), Gradle (JVM), Nx and Turborepo (JavaScript monorepos). See Monorepos and Build Tools.
Best Practices
Keep Targets Small
Fine-grained targets give better caching and parallelism, and make dependency graphs meaningful.
Enforce Visibility
Use visibility to encode architecture rules — which libraries may depend on which — and let the build enforce them.
Invest in Toolchains
Declare compilers and SDKs as toolchains rather than relying on host installations, so builds are reproducible on every machine.
Automate BUILD File Maintenance
Use generators such as Gazelle where available, so developers are not hand-editing dependency lists for every import.
Common Mistakes
Non-Hermetic Actions
Giant Catch-All Targets
Adopting Without Ownership
FAQ
What is Bazel used for?
Building and testing large codebases, especially multi-language monorepos, with reproducible results, caching, remote execution, and precise dependency tracking.
Why are Bazel builds fast?
Because actions are hermetic and content-addressed, Bazel can reuse cached results from any previous build — locally or from a shared remote cache — and run independent actions in parallel, including across remote machines.
What is Bzlmod?
Bazel's module system for external dependencies, defined in MODULE.bazel and resolved from registries such as the Bazel Central Registry. It replaces the older WORKSPACE file.
Is Bazel worth it for a small project?
Usually not. Its benefits grow with codebase size, language diversity, and team count. Small projects are typically better served by their language's standard build tools.
What is remote execution?
Running build and test actions on a cluster of remote workers instead of the local machine, sharing their results through a remote cache. It parallelizes builds far beyond one machine's cores.
Related Topics
- Build Tools — The build tool landscape
- Monorepos — Repository strategies that benefit from Bazel
- CMake — Build generation for C and C++
- CI/CD — Fast, cached pipelines
- GitHub Actions Caching — Caching in CI
- C++ — A common Bazel language
- Nix and NixOS — Reproducible builds and environments with Nix