Deployment guide

bazel-remote cache

Overview

bazel-remote is a fast, self-hosted remote build cache. It speaks the HTTP and gRPC Remote Cache (REAPI) protocols used by Bazel, Buck and Goma. It stores Content Addressable Storage (CAS) blobs and Action Cache (AC) entries on disk, with a configurable size cap and least-recently-used eviction. Your build and CI machines then share compiled artifacts and action results instead of rebuilding them from scratch.

This SecureAnyCloud image installs the official bazel-remote single static Go binary at /usr/local/bin/bazel-remote and runs it as the bazel-remote service. It serves the HTTP cache on port 8080 and the gRPC cache on port 9092, with the disk cache at /var/lib/bazel-remote/cache on the OS disk.

bazel-remote is an API service with no web UI. You use it through Bazel (or another REAPI client) by pointing --remote_cache at it, or through its HTTP REST API with curl. This guide uses curl to demonstrate the cache and shows the .bazelrc configuration.

What you get:

  • bazel-remote 2.6.1 as the official single static Go binary
  • Services. bazel-remote.service serves HTTP on :8080 and gRPC on :9092.
  • Secure by default, no default login. bazel-remote normally starts with no authentication and binds every interface, which is unacceptable for a network cache. This image never ships that. HTTP Basic Auth is enforced on both the HTTP and the gRPC interface with --htpasswd_file.
  • HTTP Basic Auth on both interfaces via --htpasswd_file, with the first-boot credential, binary refresh and info note handled by bazel-remote-firstboot.service
  • A unique credential per VM. A bazel-remote-firstboot.service oneshot generates a unique username and password on each VM's first boot and writes them to a root-only 0600 file. It also resolves the instance URL, writes a root-only info note, and then disables itself. No two VMs share a credential, and none is baked into the image.
  • Fail-secure bootstrap. The cache server carries a bootstrap gate (ConditionPathExists) that clears only after first boot has written the per-VM credential. The cache cannot start until a credential exists. If first boot were interrupted, the cache would not serve, rather than coming up open.
  • Always current. On first boot, the appliance downloads the newest bazel-remote release binary, and falls back to the baked version if the network is unavailable. Every launch therefore runs current software rather than the version frozen at build time.
  • Ubuntu 24.04 LTS base, patched at build time
  • 24/7 SecureAnyCloud support, 24h response SLA

Prerequisites

You need an active Azure subscription, an SSH key pair, and a VNet with a subnet.

The cache is light on CPU. Performance depends mostly on disk and network throughput and on cache capacity. Use Standard_B2s (2 vCPU, 4 GB RAM) for evaluation and small teams. For busy CI fleets, use Standard_D4s_v5 or larger with a bigger data disk.

The disk cache lives on the OS disk by default. Expand the disk, and raise the size cap (see Sizing the cache), before you cache large build graphs.

1

Deploy from the Azure Portal

Search the Marketplace for bazel-remote on Ubuntu 24.04 and choose your VM size.

Attach a network security group (NSG) that allows TCP 22 (SSH) from your management network. Also allow TCP 8080 (HTTP cache) and TCP 9092 (gRPC cache) from the build and CI machines that will use the cache.

Basic Auth is enforced, so the cache is safe to expose to your build fleet. Still, restrict the source ranges to networks you control.

2

Deploy from the Azure CLI

RG="bazel-remote-prod"; LOCATION="eastus"; VM_NAME="bazel-remote-01" IMAGE_URN="sac1647283583153:bazel-remote-cache-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name br-vnet --address-prefix 10.90.0.0/16 --subnet-name br-subnet --subnet-prefix 10.90.1.0/24 az network nsg create -g "$RG" --name br-nsg az network nsg rule create -g "$RG" --nsg-name br-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name br-nsg --name allow-cache --priority 110 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 8080 9092 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:bazel-remote-cache-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name br-vnet --subnet br-subnet --nsg br-nsg --public-ip-sku Standard
3

Connect via SSH

ssh azureuser@<vm-ip>
4

Verify the service and listeners

bazel-remote serves the HTTP cache on :8080 and the gRPC cache on :9092. Every request requires authentication, so an unauthenticated call to the status endpoint returns 401. Run:

sudo systemctl start bazel-remote 2>/dev/null || true
sudo systemctl is-active bazel-remote ss -tln | grep -E ':8080|:9092' echo "unauthenticated /status -> HTTP $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/status)"

The output shows the service active, both listening sockets, and HTTP 401 from the unauthenticated status call. The 401 confirms that authentication is enforced.

5

Read your per-VM cache credentials

The username and password were generated on this VM's first boot and stored in a root-only file. Read them and keep them secret. Then confirm that the per-VM credential authenticates and that an unauthenticated request does not.

sudo cat /root/bazel-remote-info.txt
U=$(sudo grep '^BAZEL_REMOTE_USER=' /root/bazel-remote-info.txt | cut -d= -f2-)
P=$(sudo grep '^BAZEL_REMOTE_PASSWORD=' /root/bazel-remote-info.txt | cut -d= -f2-)
echo "unauthenticated -> HTTP $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/status)"
echo "authenticated -> HTTP $(curl -s -o /dev/null -w '%{http_code}' -u "$U:$P" http://127.0.0.1:8080/status)"

The info note holds BAZEL_REMOTE_USER, BAZEL_REMOTE_PASSWORD and the cache URL. It is owned root:root with mode 0600. The status endpoint returns 401 without the credential and 200 with it. Basic Auth is enforced identically on the gRPC interface.

6

Store and fetch an artifact (CAS round trip)

The Content Addressable Storage keys every blob by the SHA-256 of its content. Store a blob with an authenticated PUT, then fetch it back and confirm that it matches byte for byte. Bazel does exactly this for every cached artifact.

U=$(sudo grep '^BAZEL_REMOTE_USER=' /root/bazel-remote-info.txt | cut -d= -f2-)
P=$(sudo grep '^BAZEL_REMOTE_PASSWORD=' /root/bazel-remote-info.txt | cut -d= -f2-) BLOB="hello from bazel-remote at $(date -u +%FT%TZ)"
HASH=$(printf '%s' "$BLOB" | sha256sum | awk '{print $1}') printf '%s' "$BLOB" | curl -s -u "$U:$P" -X PUT --data-binary @- "http://127.0.0.1:8080/cas/$HASH" -o /dev/null -w 'PUT /cas -> HTTP %{http_code}\n'
GOT=$(curl -s -u "$U:$P" "http://127.0.0.1:8080/cas/$HASH") [ "$GOT" = "$BLOB" ] && echo "GET /cas -> byte for byte match" || echo "GET /cas -> MISMATCH" echo "unauthenticated GET /cas -> HTTP $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/cas/$HASH)"

The PUT returns 200, and the authenticated GET returns the identical blob. An unauthenticated GET of the same key returns 401, so cached content is never served without the credential.

7

Point Bazel at the cache

Add the cache to your project's .bazelrc, or pass it on the command line. Use the username and password from Step 5. Over HTTP, put the credentials in the URL:

# .bazelrc — HTTP remote cache (username:password from /root/bazel-remote-info.txt) build --remote_cache=http://<cache-username>:<cache-password>@<vm-ip>:8080
# optional: keep going if the cache is briefly unreachable build --remote_local_fallback

To use the gRPC interface instead, point Bazel at grpc://<vm-ip>:9092 and pass the same credentials as a Basic Auth header:

# .bazelrc — gRPC remote cache. Compute the header once:
# echo -n "<cache-username>:<cache-password>" | base64 build --remote_cache=grpc://<vm-ip>:9092 build --remote_header=authorization="Basic <base64-of-username:password>"

Run any build twice. The first build populates the cache. A clean second build (bazel clean then bazel build //...) pulls the results back from the cache instead of recompiling. Share the same cache across your CI fleet so that every machine benefits.

8

Sizing the cache

The cache size is capped by BAZEL_REMOTE_MAX_SIZE_GIB in /etc/bazel-remote/bazel-remote.env (default 10 GiB). Once the cap is reached, the least recently used entries are evicted. Inspect the current setting and disk usage:

grep -E 'MAX_SIZE|DIR' /etc/bazel-remote/bazel-remote.env du -sh /var/lib/bazel-remote/cache 2>/dev/null df -h /var/lib/bazel-remote

To grow the cache, raise BAZEL_REMOTE_MAX_SIZE_GIB and run sudo systemctl restart bazel-remote.

For large caches, attach a dedicated data disk and mount it at /var/lib/bazel-remote/cache, preserving bazel-remote ownership. Raise the cap to match the disk, but keep it comfortably below the disk size to leave room for in-flight evictions.

9

Optional TLS

The cache enforces Basic Auth, but Basic Auth protects the credential on the wire only if the transport is encrypted. Restrict the NSG source ranges to networks you control, and enable TLS for traffic across untrusted networks. bazel-remote serves HTTPS and gRPCS natively when you supply a certificate and key:

# add to the service and restart (see the bazel-remote docs for --tls_cert_file / --tls_key_file)
# --tls_cert_file /etc/bazel-remote/tls/cert.pem --tls_key_file /etc/bazel-remote/tls/key.pem
# clients then use https://... and grpcs://... ; supply the CA to Bazel with --tls_certificate echo "See https://github.com/buchgr/bazel-remote for the TLS and mTLS flags."

With TLS enabled, point Bazel at https://<vm-ip>:8080 or grpcs://<vm-ip>:9092. Pass the certificate authority to Bazel with --tls_certificate.

Persistence, first boot and updates

The disk cache lives on the OS disk under /var/lib/bazel-remote/cache.

The first-boot service generates the per-VM username and password and refreshes the bazel-remote binary to the latest release. It also writes the fail-secure bootstrap marker and then disables itself, so later reboots are unaffected. The OS is patched at build time and ships with unattended security upgrades enabled.

sudo /usr/local/bin/bazel-remote --help 2>/dev/null | head -1 systemctl is-enabled bazel-remote-firstboot.service bazel-remote.service ls -ld /var/lib/bazel-remote/cache export DEBIAN_FRONTEND=noninteractive;
sudo apt-get update -y >/dev/null 2>&1 echo "held packages: $(apt-mark showhold | tr '\n' ' ')[none]"

Support

Every SecureAnyCloud deployment includes 24/7 support with a 24 hour response SLA. Contact sales@secureanycloud.com for help with deployment, configuration or scaling.

This is a repackaged open source software product with additional charges for SecureAnyCloud support services. bazel-remote and Bazel are trademarks of their respective owners. All product and company names are trademarks or registered trademarks of their respective holders. Their use does not imply any affiliation with or endorsement by them.

Deploy on Azure

Launch bazel-remote on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure