3.6 CI Basics

The first Bazel CI pipeline can be one command:

bazel test --config=ci //...

That command can be the whole starting point, but it is not a whole mental model. On a laptop, Bazel hides a lot of state behind a warm server, populated output tree, local caches, and an interactive terminal. In CI, the same command is stripped down to its contracts: which Bazel binary runs, which flags are stable, which cache layers survive the job, what exit code comes back, and whether the log is readable after everyone has gone home.

CI combines Bazel's incremental design with short-lived machines, scripted failure handling, and shared cache policy. This section teaches that boundary without jumping straight to the Level 6 infrastructure story.

One Command Carries Several Contracts

The section starts with the smallest useful pipeline, then opens up the hidden state behind it.

3.6.1 Basic CI Recipe gives the first working shape: put Bazelisk on PATH, restore the caches CI can persist, run bazel test //..., and save the caches afterward. The article is deliberately modest. It keeps the initial workflow focused on version pinning, cache persistence, and one broad test command before introducing worker pools, affected-target selection, or remote execution.

3.6.2 Bazel Server Lifecycle in CI explains why that simple pipeline still feels slower than a laptop. Bazel is a client/server tool, and the server's in-memory state is valuable. A fresh CI runner starts without loaded packages, without a warm Skyframe graph, and without analysis reuse. Disk cache and remote cache can save execution work, but they do not make cold analysis disappear.

3.6.3 CI-Specific Flags & Exit Codes then turns the command into a scriptable interface. A CI invocation needs stable log behavior and stable exit-code handling, so the section routes CI-only choices through the named-config pattern from 3.2.1 .bazelrc Hierarchy. --keep_going, --noshow_progress, --test_output=errors, and exit-code branching are not polish. They are what make a red build diagnosable instead of merely red.

Cache Is A Layered System, Not A Switch

The middle of the section is about separating cache layers that are easy to blur together.

3.6.4 Disk Cache in CI is the first performance step after the basic recipe because it needs no service. If one runner, one restored directory, or one CI cache key can carry action results between runs, --disk_cache recovers work that would otherwise be repeated after the Bazel server exits. It is local state, though. Once the useful cache is trapped on one machine, it cannot help a fleet of stateless workers.

3.6.5 Simple Remote Cache Setup moves that same action-result idea behind a network endpoint. The mental shift is not "new cache semantics" so much as "new cache boundary": multiple runners and developer machines can reuse outputs produced elsewhere. The article keeps the setup simple, with CI writing and developers reading, and leaves the deeper AC/CAS protocol, namespace, and monitoring concerns for 6.2 Shared Remote Cache.

3.6.6 Cache Warm/Cold Considerations ties the layers together. A CI run can be cold in the Bazel server, warm in a disk cache, and warm in a shared remote cache at the same time. That is the key idea that prevents bad diagnoses. A high remote-cache hit rate does not prove analysis is warm, and a suspicious cached success should be debugged with a colder path instead of by making every PR build clean by default.

For a slow CI run, preserve the invocation evidence first, then use the hypothesis-and-observable checklist from 2.4 Caching & Incrementality to identify which layer failed to reuse work. 5.5 Observability develops historical comparison and drill-down. 6.2 Shared Remote Cache covers operating the shared service.

Diagnose Cold State Before Adding Infrastructure

If CI is slow, ask which state is cold: the server, the local output tree, the repository cache, the disk cache, or the shared cache. If CI behaves differently from a laptop, compare the flag set before changing BUILD files. If cache hits look wrong, go back to the hermeticity assumptions from 2.3 Hermeticity & Sandboxing and the cache model from 2.4 Caching & Incrementality rather than treating cache purges as routine maintenance.

Grow CI From The Observed Bottleneck

If you are bringing up CI for the first time, read 3.6.1 Basic CI Recipe, then jump directly to 3.6.3 CI-Specific Flags & Exit Codes so the pipeline is both green and scriptable. Add 3.6.4 Disk Cache in CI as soon as repeated execution time matters.

If the complaint is "CI is much slower than my laptop," read 3.6.2 Bazel Server Lifecycle in CI before adding more cache infrastructure. If the job is already using multiple runners or restored local caches are not enough, continue to 3.6.5 Simple Remote Cache Setup. Once the team starts debating clean builds, cache poisoning, branch reuse, or developer upload policy, 3.6.6 Cache Warm/Cold Considerations is the article that keeps the discussion precise.

The Level 6 material is intentionally later. Warm worker pools, target determination, remote-cache operations, and retry policy matter, but they are easier to design after this section has separated three questions: what command should CI run, which state survives between runs, and which failures should automation classify differently?

think

Classify: A CI run reports many remote-cache hits but still spends a long time loading and analyzing, then the wrapper labels a test failure as a generic build failure. Which parts of the operating model are cold or misconfigured?

Reveal

Remote-cache hits warm action-output reuse. They do not preserve the Bazel server's in-memory Skyframe state, so loading and analysis can still be cold on a fresh runner. The incorrect failure label is separate: the wrapper is not classifying Bazel's exit codes, so it needs the scriptable flag and status handling from the CI-flags layer. More cache infrastructure would not repair either cold analysis or bad exit-code handling.

key takeaway

Start CI with a pinned Bazel version, stable --config=ci flags, a broad test command, and the cache state you can persist. Separate cold server analysis, action-result reuse, and exit-code or log handling before adding infrastructure.