kache 1.2.0

Compiler cache for Rust, C/C++ and CUDA, with S3, GCS, shared-filesystem and OCI remotes.
---
title: S3 setup
description: Connect Kache to AWS S3 or an S3-compatible service
---

Kache supports AWS S3, Cloudflare R2, Ceph, MinIO, and other compatible endpoints through its S3 backend.

## Configure the remote

```toml title="~/.config/kache/config.toml"
[cache.remote]
type = "s3"
bucket = "my-build-cache"
region = "eu-west-1"
```

The region defaults to `us-east-1`. Kache does not read `AWS_REGION` or `AWS_DEFAULT_REGION`, so set the bucket's region in `region` or `KACHE_S3_REGION`. With a wrong region, `kache doctor` fails the remote check and, when S3 reports it, names the bucket's actual region.

<Tabs items={["AWS S3", "Cloudflare R2", "Ceph or MinIO"]}>
  <Tab value="AWS S3">
    ```toml
    [cache.remote]
    type = "s3"
    bucket = "my-build-cache"
    region = "eu-west-1"
    profile = "build-cache"
    ```
  </Tab>
  <Tab value="Cloudflare R2">
    ```toml
    [cache.remote]
    type = "s3"
    bucket = "my-build-cache"
    endpoint = "https://<account-id>.r2.cloudflarestorage.com"
    region = "auto"
    ```
  </Tab>
  <Tab value="Ceph or MinIO">
    ```toml
    [cache.remote]
    type = "s3"
    bucket = "build-cache"
    endpoint = "https://s3.internal.example.com"
    region = "us-east-1"
    ```

    Kache uses path-style bucket addressing for custom endpoints.
  </Tab>
</Tabs>

## Credentials

Kache checks these sources in order:

1. `KACHE_S3_ACCESS_KEY` and `KACHE_S3_SECRET_KEY`
2. `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, with `AWS_SESSION_TOKEN` when it is set
3. the selected profile, or the `default` profile when none is selected: its keys, legacy inline SSO settings, then `credential_process`
4. only when no profile is selected: environment-based web identity, then ECS/task credentials, then EC2 instance credentials

Both explicit `KACHE_S3_*` credential variables are required. If only one is present, Kache warns and continues down the standard credential chain. There is no session-token variable for them, so pass temporary credentials through `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN`.

Setting `AWS_ACCESS_KEY_ID` without `AWS_SECRET_ACCESS_KEY`, or the reverse, is an error: S3 requests fail with a message that names the missing variable, and Kache tries no other source. An empty variable counts as unset.

These environment variables override the fields of an S3 remote; a `gcs`, `oci` or `filesystem` remote ignores them:

| Environment | TOML field |
| --- | --- |
| `KACHE_S3_BUCKET` | `bucket` |
| `KACHE_S3_ENDPOINT` | `endpoint` |
| `KACHE_S3_REGION` | `region` |
| `KACHE_S3_PREFIX` | `prefix` |
| `KACHE_S3_PROFILE` | `profile` |
| `KACHE_S3_USER_AGENT` | `user_agent` |

`KACHE_S3_BUCKET` alone configures an S3 remote, and a `KACHE_S3_BUCKET` that is set but empty turns off the bucket the config file names.

For a persistent daemon, put remote settings in the config file or service definition. A daemon Kache starts in the background drops the six variables above and `KACHE_LOCAL_ONLY` from the environment it inherits, and keeps the rest, including credentials, `AWS_PROFILE` and `AWS_ENDPOINT_URL_S3`. `kache stats` warns when the daemon's configuration differs from this shell's, and its `Remote` row shows the remote the daemon uses.

### Selected profile

The remote's `profile` setting (or `KACHE_S3_PROFILE`) selects a profile, and so does an `AWS_PROFILE` that is not empty. The setting wins when both are present. Kache then loads credentials from that profile only, after the explicit keys in steps 1 and 2.

If the selected profile cannot supply credentials, S3 requests fail. Kache does not fall back to web identity, ECS or EC2 instance credentials: on a build machine with an instance role, a mistyped profile name would otherwise sign every request as that role. `kache doctor` fails its `Remote access` check with the profile, the setting that selected it, and the cause when Kache can tell:

- the profile is not defined in the config or credentials file
- one of those files cannot be read
- the profile uses a form Kache does not support, listed under [Compatibility boundaries](#compatibility-boundaries)
- its SSO token has expired, or its `credential_process` failed

Kache also logs the reason as a warning when it first appears and whenever it changes, so it shows in the daemon's log.

On an EC2 machine where `AWS_PROFILE` names a profile without credentials, such as one that only sets `region` for the AWS CLI, unset `AWS_PROFILE` in Kache's environment so it uses the instance role.

Kache logs which source supplied the credentials at info level, in `kache.log` and the daemon's log, when it first signs with that source and whenever the source changes. A passing `Remote access` check names the source too, for example `credentials from EC2 instance metadata accepted`.

### Behind a proxy

Kache asks for ECS and EC2 instance credentials through the HTTP client it uses for S3, and that client honors `HTTP_PROXY`, `HTTPS_PROXY` and `ALL_PROXY`. A metadata request sent through a proxy does not reach this machine's metadata service. When a proxy is set, exclude the metadata addresses:

```bash
export NO_PROXY=169.254.169.254,169.254.170.2
```

## Compatibility boundaries

The S3 implementation signs with SigV4. It does not currently load SigV4a/Multi-Region Access Point settings, assume-role profiles using `source_profile` or `credential_source`, modern `[sso-session]` profiles, or profile-based web identity. Resolve those credentials before starting Kache or use `credential_process`. When the selected profile is one of these, `kache doctor` says which.

`AWS_ENDPOINT_URL_S3` is accepted when no Kache endpoint is set. An endpoint stored only inside an AWS shared profile is not loaded; copy it to `cache.remote.endpoint` or `KACHE_S3_ENDPOINT`.

Use `https://` for an endpoint on another machine. Over plain `http://`, request signatures and cached objects travel unencrypted, and anyone on the path can replace an object: the file hashes Kache checks are stored inside the same pack. Kache warns when it starts with such an endpoint, and `kache doctor` fails it. Plain `http://` to a loopback address, such as a local test server, is fine.

Prefixes are normalized by trimming surrounding separators and collapsing repeated `/`. Backslashes and `.` or `..` segments are rejected. A normalized prefix may point at a different object path from an older non-canonical configuration.

## Object layout and policy

```text
{prefix}/v3/manifests/{crate}/{key}.json
{prefix}/v3/packs/{crate}/{key}.tar.zst
{prefix}/v3/predictions/...
{prefix}/_manifests/...
```

The default prefix is `artifacts`. Readers need only `s3:GetObject`; writers also need `s3:PutObject`. Prefix-scoped policies must include both `v3/` and `_manifests/`.

`s3:ListBucket` is optional. `kache sync` needs it, and so does `cache.remote_key_listing = true`. Without it, S3 answers a request for a missing key with `403` instead of `404`. Kache treats a refused read as a cache miss and says so once in its log. The cost is that a real refusal, such as a policy that denies the prefix or an expired token on an existence check, also looks like a miss: the build compiles instead of failing. A download that S3 refuses with a credential error (an expired token, a bad signature, clock skew) is still reported as an error.

Remote packs use zstd; local blobs remain uncompressed. Configure the level with `cache.compression_level` or `KACHE_COMPRESSION_LEVEL`.

Each v3 entry download is limited to 8 GiB of compressed data and 8 GiB of extracted file data. Kache rejects an oversized download before restoring it; a compiler invocation recompiles when the remote entry cannot be restored. These are per-entry limits, not a total memory budget for concurrent downloads.

Buffered downloads also share a 512 MiB memory budget per Kache process, including space for temporary copying. Larger requests run alone under the existing per-entry limits. Decoders and parsed metadata use memory outside this budget.

Kache never deletes remote objects. Bound the bucket with a lifecycle rule; [Bound a remote](/docs/remote-cache/bounding) lists the prefixes and ages.

## macOS LAN access

macOS may request Local Network permission when an installed LaunchAgent reaches a LAN endpoint. If only the service gets `No route to host`, reinstall it with the current binary and check System Settings > Privacy & Security > Local Network.

Verify the result with:

```bash
kache doctor
kache daemon status
kache sync --dry-run
```