kache 0.6.0

Zero-copy, content-addressed Rust build cache. No copies, no wasted disk — just hardlinks locally and S3 for sharing.
---
title: CI setup
description: Using kache in GitHub Actions and other CI environments.
---

# CI setup

## GitHub Actions

The fastest way to add kache to a GitHub Actions workflow is the official action:

```yaml title=".github/workflows/ci.yml"
steps:
  - uses: actions/checkout@v4
  - uses: dtolnay/rust-toolchain@stable

  - uses: kunobi-ninja/kache-action@v1
    with:
      github-cache: "true"

  - run: cargo build --release
```

The action installs kache, sets `RUSTC_WRAPPER=kache`, and configures GitHub's built-in cache as the remote storage backend. No S3 bucket required — it uses the same cache infrastructure as `actions/cache`.

<Callout type="info">
`github-cache: true` is the right choice for most open-source projects. For private infrastructure or when you need to share the cache across multiple repositories, use an S3 bucket instead.
</Callout>

## Using an S3 bucket in CI

If you have an S3 bucket, configure kache via environment variables in the workflow:

```yaml
env:
  RUSTC_WRAPPER: kache
  KACHE_S3_BUCKET: my-build-cache
  KACHE_S3_REGION: us-east-1
  KACHE_S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
  KACHE_S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
```

For AWS with IAM roles (OIDC), skip the access key variables and let kache use the standard credential chain.

For S3-compatible stores (MinIO, Ceph RGW, R2, and similar), also set `KACHE_S3_ENDPOINT` to the provider's URL. `KACHE_S3_PREFIX` is optional and defaults to `artifacts`.

## Warming the cache before the build

When the daemon isn't running in CI (which is the common case for ephemeral runners), use an explicit sync step:

```yaml
steps:
  - uses: kunobi-ninja/kache-action@v1
    with:
      github-cache: "true"

  - name: Warm cache
    run: kache sync --pull

  - run: cargo build --release

  - name: Push new artifacts
    run: kache sync --push
    if: always()   # push even if the build failed, to preserve partial results
```

The `if: always()` on the push step ensures newly built artifacts are stored even when the build fails partway through. Partial cache population still speeds up future runs.

Both subcommands require a configured S3 remote (set the `KACHE_S3_*` variables above, or run `kache config`); without one they error with `No remote configured`. By default `kache sync --pull` is filtered to the crates in the current workspace's `Cargo.lock`, so an ephemeral runner only fetches what this build needs. Use `kache sync --pull --all` to pull every artifact in the bucket regardless of the local lockfile.

<Callout type="warn">
`kache sync` reports failed transfers as `(N failed)` on its progress line but still exits `0`, so CI cannot detect partial transfer failures from the exit code alone. If that matters, scan the step's stderr for `failed`.
</Callout>

## CI progress output

kache can print per-crate progress lines to stderr when `KACHE_PROGRESS` is set. It is silent by default — both in CI and locally — to avoid cargo caching the wrapper's stderr as stale compiler diagnostics. Set the variable to opt in:

| Value | Behavior |
|---|---|
| unset / anything else | Silent (default) |
| `1` or `hits` | Print hits only |
| `verbose` or `all` | Print hits, dups, and misses |

With `KACHE_PROGRESS=verbose`, lines look like:

```
[kache] serde: local hit (2ms, 1.2 MB)
[kache] tokio: local hit (3ms, 4.8 MB)
[kache] myapp: miss (1.4s, 892 KB)
```

At `hits`/`1` only the `local hit` lines appear; `miss` and `dup` lines show up only under `verbose`/`all`. The format is `[kache] <crate>: <label> (<elapsed>[, <size>])`. There is no terminal or CI detection — output depends solely on this variable.

## Coverage and instrumentation

Coverage tools like `cargo-tarpaulin` and `cargo-llvm-cov` use `-C instrument-coverage`, which changes what kache stores (path remapping is skipped to preserve source mapping for coverage reports). This produces different cache keys from regular builds, so the coverage cache is isolated automatically.

That said, many teams disable kache for coverage runs entirely to keep things simple:

```yaml
- name: Test with coverage
  run: cargo tarpaulin --engine llvm --all-features --workspace --out Json
  env:
    RUSTC_WRAPPER: ""   # disable kache for this step
```

## Disabling kache for specific steps

```yaml
- name: Some step without caching
  run: cargo build
  env:
    KACHE_DISABLED: "1"
```

Only `1` or `true` (case-insensitive) disable kache. Other values — including `0`, `false`, and empty — are ignored, so to re-enable kache in a later step you must unset the variable rather than set it to `0`.

kache still strips the rustc `-C incremental=…` flags when disabled, which avoids APFS-related corruption in git worktrees on macOS — even when caching is off.