NativeLink

Azure Blob Storage

Back the cache with an Azure Blob container: account and container naming, the endpoint override for Azurite, and when a SAS URL replaces everything else.

Who this is for: anyone running NativeLink on Azure who wants the durable tier to be a Blob Storage container. What you'll have at the end: a container-backed CAS behind a local fast tier. Time: twenty minutes, most of it access configuration.

Before you start

A config file you can edit (see Configuration), and an Azure storage account you can write to.

Azure Blob is one provider value of the same cloud object store used by S3 and compatible, GCS and OCI Object Storage. The composition around it is identical; only the provider block changes.

The recipe

stores: [
  {
    name: "CAS_MAIN_STORE",
    fast_slow: {
      fast: {
        filesystem: {
          content_path: "/var/lib/nativelink/cas/content",
          temp_path: "/var/lib/nativelink/cas/tmp",
          eviction_policy: { max_bytes: "50gb" },
        },
      },
      slow: {
        experimental_cloud_object_store: {
          provider: "azure",
          account_name: "mystorageaccount",
          container: "nativelink-cas",
          key_prefix: "cas/",
          retry: { max_retries: 6, delay: 0.3, jitter: 0.5 },
        },
      },
    },
  },
],

account_name and container are the pair you have to set. From them the store builds the endpoint https://{account_name}.blob.core.windows.net/{container}. The container is a blob container inside the storage account. Create it before starting NativeLink; the store does not create it for you.

ExperimentalAzureSpec

Three ways to point at a container

Account plus container. The default above. The endpoint is derived, and the store authenticates with Entra ID Workload Identity and nothing else: it reads AZURE_TENANT_ID, AZURE_CLIENT_ID and AZURE_FEDERATED_TOKEN_FILE (the variables AKS workload identity injects into a pod) and fails at startup if they are missing. A VM managed identity, a storage account key or an az login session are not tried. Nothing secret is in the config.

AzureBlobStore

An endpoint override. endpoint replaces the host portion only; the container is still appended to whatever you give. This is how you point the store at an emulator such as Azurite:

{
  name: "CAS_SLOW_STORE",
  experimental_cloud_object_store: {
    provider: "azure",
    account_name: "devstoreaccount1",
    container: "nativelink-cas",
    endpoint: "http://127.0.0.1:10000/devstoreaccount1",
  },
},

That address is Azurite's default. Plain http:// is accepted as-is; the Azure store does not read insecure_allow_http. Workload Identity is still the credential in this mode, so the three environment variables must be present; if the emulator cannot accept such a token, use sas_url instead.

A SAS URL. sas_url takes precedence over all of the above: when it is set, account_name, container and endpoint are ignored for URL construction and no credential is attached, because the signature in the URL is the credential.

{
  name: "CAS_SLOW_STORE",
  experimental_cloud_object_store: {
    provider: "azure",
    account_name: "mystorageaccount",
    container: "nativelink-cas",
    sas_url: "${AZURE_CAS_SAS_URL}",
  },
},

Options worth setting

FieldDefaultWhat it decides
key_prefixnoneNamespacing inside one container; give the CAS cas/ and the AC ac/
endpointderivedHost override; ignored when sas_url is set
sas_urlnoneFull pre-signed URL; overrides the derived endpoint and the credential
retryExponential backoff with jitter, shared with every other provider
consider_expired_after_s0 (never)How old a blob may be and still count as present
max_retry_buffer_per_request5 MiBHow much of a single-request upload is buffered so it can be retried
multipart_max_concurrent_uploads10Concurrent staged-block uploads within one large blob

Blobs under 5 MiB with a known size are uploaded in one request; anything larger, or of unknown size, is staged as blocks of at least 5 MiB and committed with a block list. retry and consider_expired_after_s behave as described in the shared options section; insecure_allow_http and disable_http2 are accepted but not read by the Azure store.

Steps

  1. Create the storage account and container in the region your workers run in. A standard general-purpose v2 account on hot tier is the right default; cool and archive tiers price retrieval in a way a build cache cannot afford.

  2. Grant access. Assign the workload identity the Storage Blob Data Contributor role scoped to the container, or mint a SAS with read and write. The store never lists or deletes blobs.

  3. Set the Workload Identity variables (AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE) in the process environment, unless you use sas_url. Uncommitted blocks left by a process killed mid-upload are discarded by Azure after a week on their own.

  4. Add the store as the slow half of a fast_slow pair whose fast half is a local filesystem store.

  5. Run a build twice: once cold, once after emptying the local fast tier.

You did it right if

  • Blobs appear under key_prefix in the container during the first build.
  • The second build is a cache hit even after content_path is emptied, which proves reads fall through to the container.
  • Container request counts are far lower than blob counts, which proves the fast tier is absorbing repeat reads.
  • No uncommitted blocks accumulate between builds.

When it doesn't work

NextCompose stores

Deduplication, compression and existence caching in front of the container, the layers that decide what it actually costs.

SidewaysS3 and compatible

The same store type with a different provider, and the shared options in full.

On this page