Learn · Interactive trip
budding
Learning Bazel
Fast, correct builds for growing codebases — a typed-along trip through a build system that knows exactly what every step read and wrote.
Bazel's reputation problem is not complexity. It is that nearly every tutorial hands you machinery — workspaces, repositories, toolchains — before you have felt a single problem that machinery solves. You already have working tools. go build works. tsc works. A first chapter about registry configuration asks you to pay a cost with no receipt.
This book inverts that. Every chapter opens with a failure of the tools you already have, and closes with the build system doing exactly that one thing — and nothing else. You type every file; nothing is cloned. By the end you change one line of one schema and watch a Go binary and a TypeScript bundle both follow, which is the thing neither go build nor tsc can ever do for you.
The one idea
A build system's job is to know exactly what each step read and wrote.
Sandboxing, hermeticity, the module system, toolchains, remote caching — every one of them falls out of taking that sentence seriously. Each arrives in this book only when you hit the wall it removes, and never before.
Make already contains the two ideas underneath it: a target is defined by its inputs, and work is skipped when the inputs have not changed. Make gets both slightly wrong. Those specific wrongnesses are where we start.
The five parts
I — Earning the graph
- The Makefile That Lies — five lines, one undeclared input, and a build that reports success while handing you the wrong answer — then the race the same omission becomes under
-j, and the two other tools with the same hole in them. - One Command, Two Languages — a Go binary and a TypeScript project in one workspace, and the first thing your own tools cannot do.
- Seeing Your Project as a Graph — asking what breaks if you change a file, and finding the compiler in the answer.
- The File That Isn't There — a build that refuses to read a file sitting right next to the source, and the one line that fixes it.
A five-line Makefile that lies: its recipe reads a file it never declared, so an innocent edit produces a confidently wrong artifact with no error and no warning. You build it, break it, and watch the tool report success. Then the same omission at -j2, where it stops being a stale answer and becomes a race that leaves a truncated file every later build calls up to date; three makes that read one line two different ways; and the npm script that has no dependency line to get wrong in the first place. Then the same graph expressed honestly, where the lie is structurally impossible — and a sandbox that refuses to let a compiler read what nobody declared.
II — The language you configure it in
- Who Is Allowed to Depend on This? — visibility as a boundary the build enforces, and the choice every new consumer forces.
- Macros Expand to Nothing New — a function that removes real repetition, proved to add no power at all.
- When the Generator Meets the Macro — build files written for you, and the collision that says who owns a package.
Labels, visibility, and what a rule actually is. The configuration language framed as Python that deliberately cannot do I/O — where the interesting property is not the syntax you already know but that it cannot read a file and cannot loop forever. Macros arrive as the answer to repetition you now have, with a proof that they expand to exactly what you would have typed. Then a generator writes your build files for you, collides head-on with the macro, and teaches something better than either would alone.
III — The outside world
- A Library from Outside — a third-party Go module, and the two lockfiles that appear with it.
- npm Packages Are Labels — the same job in the other language, where the build refuses to run install scripts it was not told about.
- When One File Gets Too Long — splitting the manifest, and proving the split changed nothing.
Third-party libraries, one chapter per language, because the two mechanisms differ enough that combining them shortchanges both. Toolchains, framed as the compiler is also an input. And the first real organization beat: the module manifest has grown long, so it splits.
IV — The payoff
- What the Cache Pays Back — delete every output, then rebuild in a fraction of the time.
- One Schema, Two Languages — change one line and watch both binaries follow.
A cache that turns a 45-second cold build into a 2-second one after you have deliberately destroyed every output. Then the chapter this book exists for: one schema, two languages, one line changed, both binaries following.
V — Living with it
- Why Did It Rebuild? — asking the build to explain itself, and finding that touching a file does nothing.
- The Shape of a Repository — where build logic lives, how configuration layers, and an honest account of the costs.
Why did this rebuild? — and the discovery that touching a file rebuilds nothing, because the build hashes content rather than timestamps. Adopting incrementally rather than over a rewrite weekend. An honest accounting of what gets worse. Then the shape of a repository several teams maintain.
Versions, and when this was true
Every version number in this book is a literal you will type, and every one of them will eventually be old. That is not a defect to be fixed by omitting them — a tutorial that says "install the current version" teaches nothing when the current version changes something — but it is a fact to state plainly.
As of August 2026. Bazel 9.2.0, Go 1.26.5, TypeScript 5.9.3, Node 22.22.2. The rule sets: rules_go 0.61.1, aspect_rules_ts 3.8.11, aspect_rules_js 3.2.2, rules_nodejs 6.7.4. Every transcript on these pages was produced by that exact combination.
Two things follow.
Newer versions will mostly work. The concepts here are stable — the graph, the sandbox, the cache, labels, visibility. What moves is spelling: an attribute renamed, a flag deprecated, a configuration key that changed shape between releases. Chapter ten's package-manager key is a live example, and the book says so where it bites.
When something does not match, trust your error message over this page. The tools tell you what they want, usually precisely. This book is a description of a system as it behaved on a particular day, and the system is the authority.
Links to upstream documentation age faster than the tools do. Where this book points outward it names the thing rather than only linking it, so a dead link costs you a search rather than the information.
The same idea under other names
Build systems, package managers, and the papers about them use different words for the same objects. When you leave this book for upstream documentation, a conference talk, or somebody's blog post, the vocabulary will shift under you. This is the translation table.
Action — one command with declared inputs and declared outputs. Also called a task in build-systems papers, a job in continuous integration, a step in most pipeline tools, and a recipe in Make (though Make's recipe carries no declaration, which is the whole argument of chapter one).
Target — a named thing the build can produce. Called a goal in some tools, a rule in Make — confusingly, because "rule" here means the kind of thing rather than the thing itself. A Make rule is closer to this book's target; this book's rule is closer to a function that produces targets.
Hermetic — a build whose result depends only on its declared inputs. The same property appears as reproducible, deterministic, sealed, and sandboxed — though those are not synonyms so much as different halves: sandboxing is the mechanism, reproducibility is the observable consequence, and hermeticity is the property in between.
Content addressing — naming a thing by a hash of what it contains rather than where it lives. Appears as content-addressable storage, CAS, checksum, integrity hash, digest, and fingerprint. Git calls the result an object; package lockfiles call it integrity; this book's cache calls it a key.
Label — a target's address, like //server:server. Called a coordinate in some dependency systems, a selector elsewhere, and occasionally just a path — though it is not a filesystem path, which is exactly the confusion the syntax exists to prevent.
Lockfile — the recorded result of dependency resolution. The same file is a manifest in some ecosystems (wrongly — a manifest states what you want, a lockfile records what you got), a freeze file in Python, and a resolution in the module system this book uses.
The distinction worth carrying: several of these pairs are not synonyms but different layers of the same idea — the wanted versus the got, the mechanism versus the consequence. Where a word is doing double duty, that is usually where the confusion in a discussion is coming from.
Who this is for
Any working programmer who has never used Bazel and knows a little Make without being an expert in it. Nothing here assumes a particular language — the worked examples happen to use two, because a build system earns its keep exactly where more than one toolchain has to agree, and the mechanisms are the same whichever languages you bring. If you will go on to maintain a large multi-language repository rather than only use one, the organization material is written for you specifically — it is the part hardest to find written down anywhere else.
Status
All fourteen chapters are written, and the worked examples behind them are verified end to end: every step replayed from an empty directory, every build run, every transcript on these pages captured from a real run rather than typed from memory — including the ones that fail on purpose.
Revisions happen in place. If something here is wrong or unclear, that is worth telling me about.
Seedling — complete, freshly written, and revised in place.