# Gradle and Bazel Remote Build Cache

> Use a CloudRepo Maven repository as the remote build cache for Gradle (HttpBuildCache) and Bazel (--remote_cache): create the repository, turn Overwrite Protection off, give CI a read and write token and developers a read only one.

A CloudRepo Maven repository can be the remote build cache for Gradle (`HttpBuildCache`) and for Bazel (`--remote_cache`). Both clients store a cache entry with an HTTP `PUT` and read it back with a `GET`, and the repository returns each entry byte for byte. There is no separate repository type for this: you create one repository for the cache, change one setting on it, and point your builds at it.

The settings below were measured with Gradle 9.7.1 and Bazel 8.4.2.

## Create a repository for the cache

1. Create a Maven repository for the cache only, such as `gradle-cache` or `bazel-cache`. See [Create Your First Repository](/docs/get-started/first-repository.html). Keep it apart from the repositories that hold your releases.
2. Turn **Overwrite Protection** off on that repository, from the **Overwrite Protection** card in its settings. See [Overwrite Protection](/docs/manage/repositories.html#overwrite-protection).

### Why Overwrite Protection must be off

A new repository has Overwrite Protection on, so it refuses a second upload of a key it already holds with `409 Conflict` and the message `Version already exists. Overwrites are disabled for this repository.` A build cache sends such uploads routinely: a build that runs its tasks again (Gradle’s `--rerun-tasks`), two jobs that miss the same key at the same time, or two Bazel actions whose outputs are the same bytes.

- **Gradle** logs `Could not store entry`, then `The remote build cache was disabled during the build due to errors.`, and stops using the remote cache for the rest of that build.
- **Bazel** logs a warning and does not store that action’s result, so the action misses in every later build.

Both builds still succeed, so the log is the only place this shows. With Overwrite Protection off, the repository accepts the repeated upload and both clients keep their cache.

> **Use the repository for the cache and nothing else:** With Overwrite Protection off, a credential that can write to the repository can replace any file in it, not only cache entries. Do not put releases in a cache repository.

## Create the tokens

Authenticate with repository tokens, not your password. See [Repository Tokens: Create One and Authenticate](/docs/authenticate/repository-tokens.html) for how to create one, and which account to create it as.

- **For CI**, a **Read + write** token that reaches only the cache repository. CI fills the cache.
- **For developer machines**, a **Read only** token for the same repository. It downloads cache entries; an upload with it is refused with `403 Forbidden`. Turn uploads off on developer machines too (shown for each client below), so their builds only read.

The username is the email address of the account that created the token, and the password is the token. The examples read both from two environment variables, `CLOUDREPO_USERNAME` and `CLOUDREPO_TOKEN`. Never commit a real token to version control.

## Gradle

Add the remote cache to `settings.gradle` or `settings.gradle.kts`:

`settings.gradle`

```groovy
buildCache {
    remote(HttpBuildCache) {
        url = 'https://your-org.mycloudrepo.io/repositories/your-repo/'
        push = System.getenv('CI') != null
        credentials {
            username = System.getenv('CLOUDREPO_USERNAME')
            password = System.getenv('CLOUDREPO_TOKEN')
        }
    }
}
```

`settings.gradle.kts`

```kotlin
buildCache {
    remote<HttpBuildCache> {
        url = uri("https://your-org.mycloudrepo.io/repositories/your-repo/")
        isPush = System.getenv("CI") != null
        credentials {
            username = System.getenv("CLOUDREPO_USERNAME")
            password = System.getenv("CLOUDREPO_TOKEN")
        }
    }
}
```

Then turn the build cache on, with `org.gradle.caching=true` in the project’s `gradle.properties` or `--build-cache` on the command line.

`push` is on only where the `CI` environment variable is set. GitHub Actions, GitLab CI/CD and CircleCI set `CI=true` in their jobs; on another CI service, set it in the job. On a developer machine, where `CI` is not set, Gradle reads from the cache and uploads nothing.

## Bazel

Add two lines to the workspace’s `.bazelrc`:

`.bazelrc`

```text
build --remote_cache=https://your-org.mycloudrepo.io/repositories/your-repo
build --credential_helper=your-org.mycloudrepo.io=%workspace%/tools/cloudrepo-credential-helper.sh
```

Bazel asks the credential helper for the header to send. Save this one as `tools/cloudrepo-credential-helper.sh` in the workspace and make it executable:

`tools/cloudrepo-credential-helper.sh`

```sh
#!/bin/sh
# Bazel credential helper for CloudRepo. Bazel writes a request on stdin; this answers with a
# Basic Authorization header built from two environment variables.
cat > /dev/null
printf '{"headers":{"Authorization":["Basic %s"]}}\n' \
  "$(printf '%s:%s' "$CLOUDREPO_USERNAME" "$CLOUDREPO_TOKEN" | base64 | tr -d '\n')"
```

**Terminal**

```bash
chmod +x tools/cloudrepo-credential-helper.sh
```

> **Keep the host in front of the helper:** The host before the `=` (`your-org.mycloudrepo.io=`) limits the helper to your CloudRepo host. Without it, Bazel asks the helper for every host it contacts, including module registries such as `bcr.bazel.build`, and the helper hands each of them your token.

On developer machines, add this line to your own `~/.bazelrc` so Bazel reads from the cache and uploads nothing:

`~/.bazelrc`

```text
build --remote_upload_local_results=false
```

## What the repository holds

Cache entries are ordinary files in the repository. Gradle stores each entry at the repository root, named by a 32 character key. Bazel stores its entries under `ac/` and `cas/`, named by a SHA-256 digest. A key nobody stored answers `404 Not Found`, and both clients read that as a cache miss.

To empty the cache, delete its files in the portal. See [Deleting Files and Folders](/docs/manage/repositories.html#deleting-files-and-folders).

## Events

Each cache upload is a file upload and each cache hit a file download. Each one fires the repository’s **File Upload** or **File Download** webhooks, if it has any. See [Webhooks](/docs/manage/webhooks.html). A build can store and read many entries, so leave webhooks off the cache repository unless you want one event for each.

## When it does not work

| What you see                                                                                                | Why                                                                                                   | What to do                                                                                                                      |
| ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Gradle: `Could not store entry`, then `The remote build cache was disabled during the build due to errors.` | Overwrite Protection is on: the repository refused a repeated upload with `409`.                      | Turn **Overwrite Protection** off on the cache repository.                                                                      |
| Bazel: a warning about a refused upload, and the same action misses in later builds                         | The same `409`.                                                                                       | The same.                                                                                                                       |
| `403 Forbidden` on an upload                                                                                | The token is **Read only**, or it does not reach this repository.                                     | On developer machines, turn uploads off as shown above. In CI, use a **Read + write** token that includes the cache repository. |
| `401 Unauthorized`                                                                                          | No credential, or one CloudRepo refused: a mistyped, expired or revoked token, or the wrong username. | Check the token and the username as in [When a publish is refused](/docs/publish/maven.html#when-a-publish-is-refused).         |

## Next steps

- [CI/CD overview](/docs/ci/overview.html): hold the token as a CI secret and read it from the environment.
- [Repository Tokens: Create One and Authenticate](/docs/authenticate/repository-tokens.html).
- [Repository Management](/docs/manage/repositories.html): Overwrite Protection and the Trash.

---

The page: https://www.cloudrepo.io/docs/ci/build-cache.html
