kache 0.6.0

Zero-copy, content-addressed Rust build cache. No copies, no wasted disk — just hardlinks locally and S3 for sharing.
---
title: Remote service
description: Server-side kache — a remote planner that prefetches artifacts from workspace manifests, dependency history, and build intent.
---

# Remote service

<Callout type="warn">
**SOON.** Server-side kache is the next milestone. The deployment model, auth integration, and HA behavior are still hardening — treat the planner service and Helm chart as a preview today.
</Callout>

The remote planner service warms the *right* artifacts before rustc asks for them. Where local kache reacts to cargo invoking rustc, the planner anticipates: it draws on build manifests (uploaded by `kache save-manifest`), dependency history, and the client's build intent, then advises clients which artifacts to prefetch from S3 at the start of a session.

The service lives in [`crates/kache-service`](https://github.com/kunobi-ninja/kache/tree/main/crates/kache-service). It persists planner state in an embedded SurrealDB database and serves three HTTP routes: `POST /v1/prefetch-plan` and `POST /v2/prefetch-plan` (both aliased to the same handler — clients default to `/v2`), plus `GET /healthz` and `GET /readyz` probes. It safely returns a `use_fallback` disposition when the database has no matching candidates — so clients always have a working code path even before the planner has data for them.

## What it does

- **Manifest upload.** Clients call `kache save-manifest` at the end of a build. This uploads a build manifest (and, with `--namespace`, sharded indexes keyed by `Cargo.lock`) to the configured S3 remote — each entry records cache key, crate name, compile time, and artifact size. The planner service consumes that data out of band (it is seeded from a snapshot today; see `KACHE_PLANNER_SEED_STATE_FILE`) — `save-manifest` does not POST to the service.
- **Prefetch hints.** At the start of a new build session, the client `POST`s a build intent (crate names, namespace, `Cargo.lock` deps) to the planner. The planner replies with a plan: a `disposition` (`Execute` or `UseFallback`) and, when executing, ranked `candidates` (cache key + crate name) likely to be needed.
- **Use-fallback safety.** If the planner has no service-side state, resolves no candidates, or hits an internal planning error, it returns a `UseFallback` plan rather than an HTTP error. Clients then fall back to filtering S3 prefetch by `Cargo.lock` (the same path the daemon uses today without a planner).

## Build and run

```sh
just build-service
just image-service
just image-service-release
cargo run -p kache-service
```

For local development, `cargo run -p kache-service` binds `0.0.0.0:8080` by default (override with `--bind` / `KACHE_PLANNER_BIND`) and persists the embedded SurrealDB planner database at `/var/lib/kache/planner.db` (override with `--db-path` / `KACHE_PLANNER_DB_PATH`). Pass `--token` / `KACHE_PLANNER_TOKEN` to enforce bearer auth on the standalone binary, and `KACHE_LOG` (default `kache_service=info`) to filter logs. For a containerized run, the `image-service` recipes produce a `linux/amd64` image (override `PLATFORM` in `docker-bake.hcl` to target other architectures).

## Helm chart

```sh
helm upgrade --install kache-service ./charts/kache-service
```

The chart in [`charts/kache-service`](https://github.com/kunobi-ninja/kache/tree/main/charts/kache-service) is intentionally small: one `Deployment`, one `Service`, optional `PersistentVolumeClaim`, hardened security defaults (`runAsNonRoot`, UID/GID `65532`, `readOnlyRootFilesystem`, all capabilities dropped — the writable DB path must live on a mounted volume), health probes, optional `kunobi-auth` bearer-token wiring through an existing `Secret`, and optional `kunobi-ha` Lease-based leader election. When `ha.enabled`, it also ships a `ServiceAccount` plus `Role`/`RoleBinding` granting the `coordination.k8s.io` Lease permissions leader election needs (and mounts the service-account token only under HA). It does not bundle ingress or cluster-level policy — bring your own.

### Pointing clients at the planner

Clients only consult the planner when `KACHE_PLANNER_ENDPOINT` is set (or `cache.planner.endpoint` in config) — `KACHE_PLANNER_TOKEN` alone does nothing. Point clients at the in-cluster service:

```sh
KACHE_PLANNER_ENDPOINT=http://<svc>.<ns>.svc.cluster.local:8080
# optional: KACHE_PLANNER_TIMEOUT_MS to bound the planner round-trip
```

### Bearer-token auth

Auth is enabled by pointing the chart at an existing secret. Clients must send the matching token through `KACHE_PLANNER_TOKEN`. The standalone binary enforces the same check when started with `--token` / `KACHE_PLANNER_TOKEN`; with no token configured, requests are accepted anonymously. A request that lacks valid auth when a token is set gets `401`.

```yaml title="values.yaml"
auth:
  existingSecret: kache-planner-token
  existingSecretKey: token
```

### Planner state and persistence

The service stores its embedded planner database at `/var/lib/kache/planner.db` by default (`planner.dbPath`, also `--db-path` / `KACHE_PLANNER_DB_PATH`). The chart default is `persistence.enabled: true` with `type: ephemeral` — an `emptyDir` that does **not** survive pod restarts. Switch `type` to `pvc` for durable planner state:

```yaml title="values.yaml"
planner:
  dbPath: /var/lib/kache/planner.db
  persistence:
    enabled: true
    type: pvc          # default is `ephemeral` (emptyDir, lost on restart)
    mountPath: /var/lib/kache
    size: 10Gi
```

For bootstrap / migration only, the service can still import a legacy JSON planner snapshot on startup via `KACHE_PLANNER_SEED_STATE_FILE`. New installs should ignore this knob.

### High availability

For HA deployments, enable leader election and raise the replica count. `/healthz` always returns `200`, but `/readyz` (and `prefetch-plan`) return `503` until the repository is loaded and leadership is acquired — so followers stay live-but-not-ready until they win the Kubernetes Lease:

```yaml title="values.yaml"
replicaCount: 2
ha:
  enabled: true
  leaseName: kache-service
```

The chart's `ha.*` keys map to the binary's `--ha-enabled` / `--ha-namespace` / `--ha-lease-name` flags (`KACHE_HA_ENABLED` / `KACHE_HA_NAMESPACE` / `KACHE_HA_LEASE_NAME`). The namespace resolves from `ha.namespace`, then `POD_NAMESPACE`, then the mounted service-account namespace file — startup fails if none resolve.

When combining HA with PVC-backed planner state, use storage that can be mounted by all scheduled replicas (e.g. ReadWriteMany), or keep `replicaCount: 1`. The Lease itself is fine across replicas — the constraint is the planner DB volume.

## What you get without the service

Local caching, S3 sync, and `cargo metadata`-driven prefetch all work without the planner. The planner is purely additive: it raises the hit rate on the *first* build of a new branch or runner where `cargo metadata` alone would underprefetch. If you don't run the service, clients silently behave as they do today.