Skip to content

Bazel Build System Guide — Complete Polyglot Build Automation

DodaTech Updated 2026-06-23 6 min read

In this tutorial, you'll learn about Bazel Build System Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Bazel is a build and test system developed by Google that supports multiple languages in a single Repository with hermetic, reproducible builds, incremental Caching, and parallel execution across distributed workers.

What You'll Learn

You'll learn how to write BUILD files with Bazel rules, configure workspaces and external dependencies, create custom rules with Skylark (Starlark), set up remote Caching, and build multi-language monorepos.

Why Bazel Matters

Bazel solves the Monorepo build problem — hundreds of thousands of targets in multiple languages built with absolute reproducibility. Its hermetic build model guarantees that the same source always produces the same output, eliminating environmental inconsistencies.

Real-World Use

The Doda Browser rendering engine uses Bazel to build WebAssembly modules written in Rust, C++ layout engines, and JavaScript bundles — all within a single Repository with shared Caching across developer machines and CI agents.

Prerequisites

  • Familiarity with Java, C++, or Go
  • Understanding of build concepts (compilation, linking, testing)
  • Bazel 7.x installed

Step 1: Workspace and BUILD File Setup

A Bazel workspace is a directory containing a WORKSPACE file and one or more BUILD files that define targets.

# WORKSPACE — empty for a basic setup, or with external dependency declarations
workspace(name = "myproject")
# BUILD — in the root directory
load("@rules_cc//cc:defs.bzl", "cc_binary")

cc_binary(
    name = "hello",
    srcs = ["hello.cc"],
    deps = [
        "//lib:greeter",
    ],
)
# lib/BUILD
load("@rules_cc//cc:defs.bzl", "cc_library")

cc_library(
    name = "greeter",
    srcs = ["greeter.cc"],
    hdrs = ["greeter.h"],
    visibility = ["//main:__pkg__"],
)

Expected output: Running bazel build //:hello compiles the greeter library first, then links the hello binary. Subsequent builds use cached outputs.

Step 2: Multi-Language Builds

Bazel supports multiple languages in the same Repository. This example combines Go and Java targets.

# BUILD — Go binary
load("@io_bazel_rules_go//go:def.bzl", "go_binary")

go_binary(
    name = "server",
    srcs = ["server.go"],
    deps = [
        "//proto:api_go_grpc",
    ],
)
# BUILD — Java library
load("@rules_java//java:defs.bzl", "java_library")

java_library(
    name = "client",
    srcs = glob(["src/main/java/**/*.java"]),
    deps = [
        "@maven//:com_google_protobuf_protobuf_java",
        "//proto:api_java_proto",
    ],
)
# BUILD — protobuf definitions compiled to both Go and Java
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
load("@rules_java//java:defs.bzl", "java_proto_library")

proto_library(
    name = "api_proto",
    srcs = ["api.proto"],
)

go_proto_library(
    name = "api_go_grpc",
    importpath = "myproject/proto/api",
    proto = ":api_proto",
)

java_proto_library(
    name = "api_java_proto",
    deps = [":api_proto"],
)

Expected behavior: Running bazel build //... builds all targets across all languages. Bazel parallelizes independent targets and reuses cached results from previous builds.

Step 3: External Dependencies

Bazel fetches external dependencies through Repository rules defined in the WORKSPACE file.

# WORKSPACE with external dependencies
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

# Google Test for C++ tests
http_archive(
    name = "googletest",
    url = "https://github.com/google/googletest/archive/release-1.12.1.tar.gz",
    sha256 = "81964fe578e9bd7c94dfdb09c8e4d6e6759e19967e397dbea48d2c10e4d4372f",
    strip_prefix = "googletest-release-1.12.1",
)

# Maven dependencies for Java
load("@rules_jvm_external//:defs.bzl", "maven_install")

maven_install(
    artifacts = [
        "com.google.code.gson:gson:2.10.1",
        "org.junit.jupiter:junit-jupiter-api:5.10.0",
    ],
    repositories = [
        "https://repo1.maven.org/maven2",
    ],
)

Expected behavior: On first build, Bazel downloads and caches all external dependencies. Subsequent builds use the cached versions unless the checksum in WORKSPACE changes.

Step 4: Remote Caching and Execution

Bazel can share build outputs across machines using a remote cache or execute actions on remote workers.

# .bazelrc configuration for remote caching
build --remote_cache=grpc://cache.example.com:9092
build --remote_upload_local_results=true

# Use Google Cloud Storage as a remote cache
build --remote_cache=https://storage.googleapis.com/my-bucket/cache
build --google_credentials=/path/to/creds.json

# Remote execution
build --remote_executor=grpc://build.example.com:9090
build --remote_instance_name=projects/my-project/instances/default

Expected behavior: After populating the remote cache, running bazel build on a clean CI machine downloads cached outputs instead of rebuilding. Remote execution distributes compilation across a cluster of workers.

Architecture

flowchart LR
    subgraph "Workspace"
        WS[WORKSPACE]
        BUILD[BUILD Files]
        SRC[Source Code]
    end
    subgraph "Bazel Engine"
        EVAL[Target Graph]
        DEP[Action DAG]
        CACHE[Local Cache]
    end
    subgraph "Remote"
        RCACHE[Remote Cache]
        REXEC[Remote Execution]
    end
    subgraph "Outputs"
        BIN[Binaries]
        TEST[Test Results]
    end
    WS --> EVAL
    BUILD --> EVAL
    SRC --> DEP
    EVAL --> DEP
    DEP --> CACHE
    CACHE --> RCACHE
    DEP --> REXEC
    RCACHE --> BIN
    REXEC --> BIN
    DEP --> TEST

Common Errors

1. no such package @googletest// The external dependency was not fetched correctly. Run bazel sync to re-fetch all external repositories, or verify the http_archive URL and checksum.

2. ERROR: /BUILD:1:10: name 'cc_binary' not defined The Starlark rule is not loaded. Use load("@rules_cc//cc:defs.bzl", "cc_binary") at the top of the BUILD file. Rule sets must be explicitly imported.

3. Action failed because input is not declared Bazel's sandboxing prevents access to undeclared files. All inputs must be declared as dependencies or data attributes. This ensures hermetic builds.

4. Cycle in dependency graph Target A depends on B, and B depends on A. Bazel detects cycles at analysis time and errors before building. Break the cycle by restructuring the dependency.

5. Remote cache returned HTTP 403 Credentials for the remote cache are missing or expired. Verify --google_credentials path or configure authentication for the cache server.

Practice Questions

1. What makes a Bazel build hermetic? Hermetic builds declare every input — source files, tools, and dependencies — explicitly in BUILD files. Sandboxing prevents accidental access to undeclared files. The same inputs always produce the same outputs.

2. How does Bazel handle multi-language builds? Each language has a set of Starlark rules (rules_go, rules_cc, rules_java). Targets in different languages can depend on each other through generated artifacts like protobuf stubs.

3. What is the difference between bazel build and bazel run? bazel build compiles targets and produces output files. bazel run builds (if needed) and then executes the resulting binary, handling stdin/stdout for the user.

4. Challenge: Custom Starlark rule Write a Starlark rule that reads a YAML file and generates a C++ header file with the YAML keys as constants. Use the ctx.actions.write action to produce the output.

5. Challenge: Multi-platform build Configure Bazel to build the same Go binary for linux/amd64, linux/arm64, and darwin/amd64 platforms using --platforms flags and a custom toolchain configuration.

FAQ

How does Bazel compare to Make?

Make operates at the file level with shell commands. Bazel operates at the target level with sandboxed, parallel actions, cryptographic Caching, and remote execution. Bazel is designed for large-scale monorepos.

Can Bazel build Docker images?

Yes. The rules_docker Repository provides container_image, container_push, and container_layer rules for building Docker images as Bazel targets with layer Caching.

Is Bazel free and open-source?

Yes. Bazel is Apache 2.0 licensed and maintained by Google with contributions from the community. Commercial support is available through EngFlow and other vendors.

Next Steps

  • Compare Bazel with Maven for Java-only projects to understand trade-offs
  • Learn how Docker images fit into Bazel build pipelines
  • Explore the Go rules for building Go services with Bazel
  • Review the Monorepo build tools guide for alternatives like Nx and Turborepo

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro