S3 setup
kache supports any S3-compatible storage: AWS S3, Cloudflare R2, Ceph, MinIO, and others. The configuration is the same regardless of provider — you only need to adjust the endpoint and credentials.
Minimal configuration
[cache.remote]
type = "s3"
bucket = "my-build-cache"
With just a bucket name and no endpoint, kache uses AWS S3 with the default credential chain (environment variables, ~/.aws/credentials, IAM role).
If you omit region, kache defaults to us-east-1 rather than your bucket's actual region. Set region to your bucket's region for AWS S3, and to whatever value your provider expects (or auto) for S3-compatible endpoints.
Provider examples
[cache.remote]
type = "s3"
bucket = "my-build-cache"
region = "eu-west-1"
profile = "my-aws-profile" # omit to use default profileFor CI, prefer IAM roles or environment variables over a stored profile.
[cache.remote]
type = "s3"
bucket = "my-build-cache"
endpoint = "https://<account-id>.r2.cloudflarestorage.com"
region = "auto"Set credentials via KACHE_S3_ACCESS_KEY and KACHE_S3_SECRET_KEY or an AWS profile pointing to R2 API tokens.
[cache.remote]
type = "s3"
bucket = "build-cache"
endpoint = "https://s3.internal.example.com"
profile = "ceph"The profile field refers to a named profile in ~/.aws/credentials or ~/.aws/config. Region is optional for Ceph but you can set it to any non-empty string if your setup requires it.
kache always uses path-style addressing (https://endpoint/bucket/key) for every provider, so self-hosted S3 works without virtual-hosted/bucket-subdomain DNS. This also makes dotted bucket names safe on custom endpoints.
Credential resolution order
When kache needs S3 credentials, it checks these sources in order:
KACHE_S3_ACCESS_KEY+KACHE_S3_SECRET_KEY(explicit env var override)- AWS profile from
KACHE_S3_PROFILEenv var orprofileconfig field - Standard AWS chain:
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYenv vars, then~/.aws/credentials [default], then IAM instance/task roles
For CI, option 1 (explicit env vars) or option 3 (IAM roles) are the most common. For local setups with multiple AWS accounts, option 2 (named profile) keeps credentials organized.
Both halves of the explicit pair are required. If only KACHE_S3_ACCESS_KEY or only KACHE_S3_SECRET_KEY is set, kache logs a warning and falls back to the AWS chain rather than erroring — so a missing half surfaces as confusing "wrong credentials" behavior, not a clear failure.
Environment overrides
Every remote config field has a matching KACHE_S3_* env var, which takes precedence over the config file. This is handy in CI, where setting env vars is easier than shipping a config file:
| Env var | Overrides config field |
|---|---|
KACHE_S3_BUCKET | bucket |
KACHE_S3_ENDPOINT | endpoint |
KACHE_S3_REGION | region |
KACHE_S3_PREFIX | prefix |
KACHE_S3_PROFILE | profile |
KACHE_S3_ACCESS_KEY / KACHE_S3_SECRET_KEY | (explicit credentials) |
S3 bucket layout
Each cached entry is two objects — a packed tarball plus a small JSON manifest used for existence checks and listing:
{prefix}/v3/packs/{crate_name}/{cache_key}.tar.zst # the packed artifacts (zstd-compressed tar)
{prefix}/v3/manifests/{crate_name}/{cache_key}.json # small manifest for existence/listing
The default prefix is artifacts. Organizing by crate name makes filtered listing efficient — kache sync --pull issues one ListObjectsV2 per crate against {prefix}/v3/manifests/{crate_name}/, so it only enumerates manifests for crates in your Cargo.lock rather than scanning the whole v3/manifests/ tree. Use kache sync --pull --all to list everything.
kache save-manifest also writes build manifests under a separate {prefix}/_manifests/ namespace (and, when a namespace and Cargo.lock are present, content-addressed shards under {prefix}/_manifests/v3/{namespace}/shards/{hash}.json). Bucket policies that scope by prefix must grant access to _manifests/ as well as v3/.
Bucket policies
kache needs s3:GetObject, s3:PutObject, and s3:ListBucket on the bucket. For read-only CI runners that pull but don't push, s3:GetObject and s3:ListBucket are sufficient. If you restrict the policy by prefix, cover both {prefix}/v3/* and {prefix}/_manifests/* (see S3 bucket layout).
Compression
zstd compression applies to the remote .tar.zst packs only; the local blob store is kept uncompressed. The default level is 3 — fast to compress and decompress, with reasonable size reduction. Lower levels (1–2) cut compression overhead when network bandwidth matters less than CPU time; higher levels (up to 22) are available but rarely worth it for build artifacts. Values are clamped to the 1–22 range.
KACHE_COMPRESSION_LEVEL is read when the process that does the compressing starts, so set it on the uploader rather than on a plain cargo build:
KACHE_COMPRESSION_LEVEL=1 kache sync --push # fastest, larger packs
The daemon loads its config once at startup, so changing KACHE_COMPRESSION_LEVEL for a single cargo build does not reconfigure an already-running daemon — restart the daemon with the var set if uploads run through it.