Skip to content

Bazel First-Time Setup

This document describes how to configure Bazel to connect to an EngFlow Remote Execution cluster.

You also need a credential

This page covers only the project configuration your whole repository shares. You also need a credential of your own. Get one from your cluster's Getting Started page at https://<CLUSTER_ENDPOINT>/gettingstarted, then add the matching flags when you set up .bazelrc.

1. Prepare a Docker container image for remote actions

Refer to Create a container image for remote actions, which describes the requirements for a container image, how to build a new image, and where to store your image for best performance.

If you already have a container image you use for local execution in CI, make sure it meets the requirements, then use that. Ideally, your local and remote environments are similar so actions may run either locally or remotely.

To proceed, you'll need to know your container image's URL, including its SHA-256 sum. A complete URL looks like docker://gcr.io/bazel-public/ubuntu2004-java11@sha256:69a78f121230c6d5cbfe2f4af8ce65481aa3f2acaaaf8e899df335f1ac1b35b5. If your container image is stored on a private registry, you'll also need to ensure EngFlow workers are authorized to pull from it. Ask EngFlow support if you're unsure.

2. Create a remote platform configuration

In Bazel, a platform expresses an environment where an action can run. When performing remote execution, Bazel is instructed to build for a special EngFlow platform. This platform carries the Docker image and other environmental configuration to the remote execution service.

Skip this step if your project already defines a platform target for the remote environment. Add the constraints or exec_properties you need to that target instead of defining a second one.

Match your workers' operating system and CPU

The constraint_values must match the operating system and CPU architecture of your remote workers. The example below is Linux on x86_64. If your workers run Windows, start from //platform/windows_x64 instead, and read Windows remote execution for the differences that affect containers, toolchains, and paths.

BUILD
platform(
    name = "platform",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    exec_properties = {
        "container-image": "docker://gcr.io/bazel-public/ubuntu2004-java11@sha256:69a78f121230c6d5cbfe2f4af8ce65481aa3f2acaaaf8e899df335f1ac1b35b5",
    },
)

If your remote actions require internet access, add "dockerNetwork": "standard" to exec_properties.

For an example, see //platform/linux_x64 or //platform/windows_x64 in our example repository.

3. Create a C++ toolchain configuration.

To construct C/C++ commands that can run remotely, Bazel needs a C++ toolchain configuration. If you have installed a C++ toolchain in your container image, refer to Create a Bazel C++ toolchain configuration for remote actions.

A C++ toolchain configuration embeds compiler, header, and library paths that are specific to your container image, so you generate it rather than copy it from elsewhere. The fastest starting point is //platform/linux_x64 or //platform/windows_x64 in our example repository, which each pair a generated toolchain with the matching platform target.

You will likely need a C++ toolchain, even if you don't develop C++ code directly. Many Bazel rulesets use C++ for tools and wrappers.

4. Set up .bazelrc.

Info

User-specific configuration can be included in .bazelrc using the try-import directive, typically at the end of the file. For example:

.bazelrc
try-import .bazelrc.user

Configure Bazel to use the EngFlow platform in .bazelrc.

  1. Add common flags:

    .bazelrc
    build:engflow --define=EXECUTOR=remote
    build:engflow --disk_cache=
    build:engflow --experimental_inmemory_dotd_files
    build:engflow --experimental_inmemory_jdeps_files
    build:engflow --incompatible_strict_action_env=true
    build:engflow --remote_timeout=600
    build:engflow --nolegacy_important_outputs
    build:engflow --grpc_keepalive_time=30s
    build:engflow --experimental_remote_cache_compression=true # (1)
    build:engflow --execution_log_compact_file=execution_log.binpb.zst # (2)
    build:engflow --experimental_build_event_upload_strategy=remote # (3)
    
    1. Compress uploads and downloads between Bazel and the cluster for faster data transfer. This is recommended for Bazel 5 onwards.
    2. Log executed spawns into the specified file. Used to display invocation-level insights in the UI. Stable and supported from Bazel 7.4.0 onwards.
    3. Upload build events surfaced as invocation-level insights in the UI.
  2. Add platform flags. Replace <YOUR_PLATFORM_PACKAGE> with the package where you defined the platform target in step 2:

    .bazelrc
    1
    2
    3
    build:engflow --host_platform=//<YOUR_PLATFORM_PACKAGE>:platform
    build:engflow --platforms=//<YOUR_PLATFORM_PACKAGE>:platform
    build:engflow --extra_execution_platforms=//<YOUR_PLATFORM_PACKAGE>:platform
    

    The package is the directory holding that BUILD file, relative to your workspace root, and the part after the colon is the target name. Our example repository defines platform(name = "linux_x64") in platform/linux_x64/BUILD and registers it like this:

    .bazelrc
    1
    2
    3
    4
    build:remote_linux_x64 --extra_execution_platforms=//platform/linux_x64
    build:remote_linux_x64 --host_platform=//platform/linux_x64
    build:remote_linux_x64 --platforms=//platform/linux_x64
    build:remote_linux_x64 --extra_toolchains=//platform/linux_x64:cc-toolchain
    

    //platform/linux_x64 is shorthand for //platform/linux_x64:linux_x64, since the target and its directory share a name. Naming the config after the platform — remote_linux_x64, remote_macos_arm64, remote_windows_x64 — lets you choose the remote environment on the command line with --config.

    The last flag registers the C++ toolchain from step 3 through toolchain resolution, which is how current Bazel selects a C++ toolchain. It replaces the --crosstool_top, --cpu, and --compiler flags in the next substep, which are no-ops on Bazel 8 and later.

  3. On Bazel 7 and earlier, add flags for the container's C++ toolchain instead of --extra_toolchains. Make sure to set --crosstool_top to a cc_toolchain_suite target, and --cpu and --compiler to match an entry in its toolchains attribute. Also set the --host_* versions of these flags unless you're cross-compiling with a different toolchain. Replace <YOUR_CC_TOOLCHAIN_PACKAGE> with the package holding the toolchain you created in step 3.

    .bazelrc
    1
    2
    3
    4
    5
    6
    7
    build:engflow --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
    build:engflow --crosstool_top=//<YOUR_CC_TOOLCHAIN_PACKAGE>:toolchain
    build:engflow --host_crosstool_top=//<YOUR_CC_TOOLCHAIN_PACKAGE>:toolchain
    build:engflow --cpu=k8
    build:engflow --host_cpu=k8
    build:engflow --compiler=clang
    build:engflow --host_compiler=clang
    
  4. If Java is required, optionally use the hermetic JDK shipped with Bazel:

    .bazelrc
    build:engflow --java_runtime_version=remotejdk_11
    
  5. Add the remote executor flags. For the <CLUSTER_ENDPOINT>, use the host name or IP address of the Load Balancer. You may also have to specify the port, if using a non-standard one. The default ports are 443 for grpc/s, and 80 for http/s.

    Before you go on, go back to your cluster's Getting Started page at https://<CLUSTER_ENDPOINT>/gettingstarted and get your credential — an mTLS certificate or a JWT token, depending on how your cluster authenticates. You add the matching flags in the next step, and remote builds fail without them.

    If you use TLS, connect over grpcs:

    .bazelrc
    1
    2
    3
    4
    5
    6
    build:engflow --remote_cache=grpcs://<CLUSTER_ENDPOINT>
    build:engflow --remote_executor=grpcs://<CLUSTER_ENDPOINT>
    build:engflow --bes_backend=grpcs://<CLUSTER_ENDPOINT>
    build:engflow --bes_results_url=https://<CLUSTER_ENDPOINT>/invocation/
    # Only if your cluster uses a private CA — see the note below:
    build:engflow --tls_certificate=<PATH>/engflow-remote-execution/engflow-ca.crt
    

    You usually don't need --tls_certificate

    Leave --tls_certificate out if your cluster's server certificate comes from a public CA, which is the common case. Bazel already trusts public CAs.

    Add it only when your cluster uses a private or internal CA that Bazel does not trust, and point it at that CA's certificate.

    If you don't use TLS at all:

    .bazelrc
    1
    2
    3
    4
    build:engflow --remote_cache=grpc://<CLUSTER_ENDPOINT>
    build:engflow --remote_executor=grpc://<CLUSTER_ENDPOINT>
    build:engflow --bes_backend=grpc://<CLUSTER_ENDPOINT>
    build:engflow --bes_results_url=http://<CLUSTER_ENDPOINT>/invocation/
    
  6. Add your authentication flags. Which flags you need depends on how your cluster authenticates gRPC clients. Your cluster's Getting Started page at https://<CLUSTER_ENDPOINT>/gettingstarted shows the mechanism it uses, lets you create a credential, and emits the exact flags to copy.

    Mechanism Bazel flags Where to read more
    mTLS client certificate --tls_client_certificate, --tls_client_key Build the example project; mTLS
    Credential helper (ef) --credential_helper=<CLUSTER_HOSTNAME>=ef Use ef as a credential helper; Credential helper
    Cluster-issued JWT --remote_header and --bes_header, each set to x-engflow-auth-method=jwt-v0 and to x-engflow-auth-token=<token> JWT
    OIDC federation --remote_header=x-engflow-auth-method=oidc and --remote_header=Authorization=Bearer <token> OIDC federation
    GitHub token (deprecated) --remote_header=Authorization=Bearer <token> GitHub Tokens

    For --credential_helper, <CLUSTER_HOSTNAME> is a bare host name: the scope takes no port, unlike <CLUSTER_ENDPOINT> above.

    --remote_header applies to the remote cache and executor only, so for any header-based mechanism, pass the same headers with --bes_header as well. Otherwise build event uploads fail to authenticate. TLS client credentials apply to every channel, so mTLS needs no header flags.

    Credentials are per user, so put these flags in .bazelrc.user rather than the checked-in .bazelrc.

    --tls_certificate is not a client credential

    Bazel's --tls_certificate flag configures the CA that Bazel trusts to sign your cluster's server certificate, for the case where that CA is not one Bazel already trusts. Do not point it at a client certificate; use --tls_client_certificate and --tls_client_key for that.

Usage

We recommend using the latest available Bazel version.

Bash
bazel build --config=engflow //foo:bar