kache 0.14.0

Zero-copy, content-addressed build cache for Rust, C/C++ and more, with S3 and shared-filesystem remotes.
---
title: Filesystem setup
description: Configure a shared filesystem as the kache remote cache.
---

# Filesystem setup

A filesystem remote stores the same versioned cache objects as S3 in a shared
directory. It is useful when every builder can mount the same NFS, SMB, or other
shared filesystem and no object-storage service is needed.

## Minimal configuration

```toml title="~/.config/kache/config.toml"
[cache.remote]
type = "filesystem"
path = "/mnt/shared-kache"
prefix = "artifacts"
```

Use an absolute `path` so the wrapper, daemon, and `kache sync` resolve the same
directory regardless of their working directory. Every machine that reads or
writes the remote must mount that directory and have suitable permissions.

Treat every writer to the shared directory as trusted. A filesystem remote is
not a security boundary: a peer that can replace cache paths or create symlinks
can influence what other machines read or overwrite. Use S3 with access controls
when writers are not mutually trusted.

<Callout type="info">
  kache currently compiles the `s3` and `filesystem` remote types. Other
  OpenDAL services are not included in the binary.
</Callout>

## Atomic writes

Writes are staged in a temporary directory and atomically renamed into their
final location, so readers never observe a partially written pack or manifest.
By default, the staging directory is `<path>/.kache-tmp`.

Override it only when the default is unsuitable:

```toml title="~/.config/kache/config.toml"
[cache.remote]
type = "filesystem"
path = "/mnt/shared-kache"
prefix = "artifacts"
atomic_write_dir = "/mnt/shared-kache/.staging"
```

<Callout type="warn">
  `atomic_write_dir` and `path` must be on the same filesystem. The final
  rename cannot be atomic across filesystems and will fail with a cross-device
  error. In particular, do not use the machine's `/tmp` unless it is on the
  same filesystem as the shared cache. kache checks this when it connects and
  refuses the remote with an explicit message rather than failing every write.

  It must also sit outside the object tree (`<prefix>/v3/...`); otherwise
  in-progress staging files are listed as if they were cached objects. The
  default `<path>/.kache-tmp` already satisfies both rules.
</Callout>

<Callout type="info">
  A shared cache directory that other users can write is a trust boundary. kache
  verifies that each write resolves inside the configured `path`, so a symlink
  planted under the cache cannot redirect writes elsewhere, and it rejects keys
  whose components end in a dot or space (Windows strips those, which would make
  two distinct keys collide). This is defense in depth: provision the directory
  with restrictive permissions rather than relying on it.
</Callout>

## Directory layout

With the minimal configuration, the shared directory contains:

```text
/mnt/shared-kache/
├── .kache-tmp/
└── artifacts/
    ├── v3/
    │   ├── manifests/{crate_name}/{cache_key}.json
    │   └── packs/{crate_name}/{cache_key}.tar.zst
    └── _manifests/
```

`prefix` keeps kache's objects in their own subtree. The logical layout and
`kache sync` behavior are the same for S3 and filesystem remotes. Filesystem
prefixes cannot contain `:` so the same config cannot become a Windows drive
path or alternate data stream.

Keep `KACHE_CACHE_DIR` on fast local storage for each machine. Only
`cache.remote.path` should point at the shared mount; the local store is not
safe to share between machines.

## Reclaiming space on the share

Uploads are safe from any number of machines at once: objects are
content-addressed and immutable, every write lands via a temp file plus atomic
rename, and two machines publishing the same key are writing identical bytes.
Deletion has no such story. Nothing coordinates a delete against another
machine's in-flight read or upload of the same object, so **removing objects
from the shared folder is a manual, single-writer operation**.

`kache gc` and the automatic size-pressure eviction apply to a machine's own
local store only. They never delete from the shared folder — a local eviction
just means that machine pulls the object again on its next miss.

When the share does need reclaiming, pick one of:

- **A single designated host.** Nominate one machine (or a scheduled job) as
  the only thing that ever deletes from the share, and run it when the fleet is
  idle.
- **Prune by age, offline.** Stop or pause the writers, then delete whole
  `{cache_key}` pairs — the `.json` manifest and its `.tar.zst` pack — older
  than your retention window. Deleting a pack while leaving its manifest makes
  peers fetch a manifest whose pack 404s; they degrade to a compile, but you
  are paying for a lookup that can never succeed.
- **Start over.** For a small team, deleting the whole `artifacts/` subtree and
  letting the next builds repopulate it is often simpler than partial pruning.

Cross-host coordinated deletion is tracked as phase 2 of
[#414](https://github.com/kunobi-ninja/kache/issues/414). Until it lands, treat
the share as append-mostly.