Skip to content

Remote caching

A local cache makes your repeat runs instant, and it needs no setup — it’s on by default for every vx run. A shared cache extends that across machines: CI restores what a teammate already built, and a fresh clone is fast on its first run.

The key point is that the same content-addressed key works on every machine: if your teammate built a task, its result is stored under a key that your CI runner computes identically. So a fresh clone with an empty local cache doesn’t rebuild — it looks the key up remotely, downloads the artifact once, and hydrates its local cache so the next run is instant too. Reads are local-first (never pay the network for something you already have); writes upload in the background and never block or fail the build:

The payoff: pair a shared cache with --affected and a typical PR executes only the few packages it changed and downloads everything else — CI that would take minutes finishes in seconds, on a machine that never ran most of the code.

Sharing is the only part that needs a server. A solo developer needs nothing here — the local cache is automatic.

Core ships no HTTP cache client. Sharing a cache is a plugin concern: core defines a three-call RemoteCacheLayer seam (has/get/put) and a cache plugin capability, and everything else — read-through with local hydration, at-most-once in-flight deduplication, background write-through uploads, and the never-fail contract — is core’s LayeredCache. A plugin provides the wire; LayeredCache provides the behavior.

Reads try local first, then remote (hydrating local on a remote hit), with a background prefetch pass that overlaps remote GETs with execution. Writes go to local immediately; the remote upload is a fire-and-forget background task drained at end of run — failures are logged but never fail the build.

The first-party remote cache is a self-hosted platform documented in its own section. Connect a deployment and every vx run layers its shared artifact store on top of the local cache automatically — the cache is trust-scoped, so a fork PR can warm off main without being able to poison a trusted build. See Remote caching and the platform overview.

Because the wire is a plugin, you can back the shared cache with anything — your own server, a Turborepo-compatible cache, S3/R2, Redis — with no platform involved. Implement core’s RemoteCacheLayer seam and wrap the local cache in LayeredCache; the runnable recipe (including a Turbo-wire variant that speaks /v8/artifacts/:hash) is in Core is provider-neutral. Embedders that already hold a client can inject it per-run via RunOptions.remoteCache (explicit injection wins over the plugin consult).

Whatever backend fills the seam, the remote cache is fully optional at runtime. Any failure — a 500, a timeout, an auth error, a corrupt artifact — degrades to a local cache miss and the run continues. A remote outage slows you down; it never fails you.

The vx-native /v1/cache wire attaches an x-vx-digest header — a structural hash over the artifact bytes — to every upload; the client verifies it against the received bytes on download, so a corrupt store or a truncating proxy degrades to re-execution rather than restoring corrupt outputs. A bring-your-own backend can adopt the same contract.

Set the connection as CI secrets and you’re done — see Continuous integration for a complete GitHub Actions example. Pair the shared cache with --affected and most PRs only execute the packages they actually changed; everything else restores from a previous build.