Cache
Frazer Cache is one cache per site that your runners, devboxes, agent sandboxes and serverless builds all share, so nothing is downloaded or compiled a hundred times. It runs on the same servers as the jobs it serves, on your own servers or on the Frazer fleet. Only trusted builds write what others read, every shared entry is signed by its writer, and a cache outage makes a build slower but never fails it.
Product overview: GitHub runners and Cache.
How it works
Section titled “How it works”A cache belongs to a host project at one site (for example acme-infra at ovh-vint-hill) and is shared with the projects that use it. It holds four tiers:
| Tier | Holds | Shared how | Evicted by default |
|---|---|---|---|
| 1. Dependencies | Go modules, OCI images, npm, pnpm, PyPI and uv packages, Maven, crates | Across every project the cache is shared with; the client verifies content (go.sum, lockfile hashes, digests) |
Unused for 30 days |
| 2. Build outputs | Action-to-result maps and their blobs: GOCACHEPROG, Bazel, sccache, Gradle, Maven, Turborepo, Nx, BuildKit, actions/cache |
Per namespace, under write rules | Least recently used; unused for 14 days |
| 3. Warm disks | A disk per repository and branch family: Git objects, toolchains, node_modules and the pnpm store, uv cache, BuildKit state |
Cloned copy-on-write for each job; committed only by clean default-branch jobs and prebuilds | Idle for 7 days; 3 generations kept |
| 4. Node hot tier | Blobs in use on this server, and the bases of warm-disk generations | A local copy on each server’s NVMe, never the only copy | At disk-pressure marks of 80, 90 and 95 % |
Nothing in the cache is the only copy of anything a build needs: every tier can be rebuilt from upstream or from source. Caches are never shared between organizations, not even for public dependencies.
A read takes the first hit in this order: the job’s own overlay, its branch, the pull request’s base branch, the default branch. For Go, Bazel and sccache, whose keys hash every input, a read can also fall back to the organization-wide toolchain namespace that protected CI publishes, so two repositories share an entry only when they compiled the same thing with the same toolchain.
- Cache: the service at one site, with its tiers, capacity, quotas and endpoints.
- Cache policy: which ecosystems are on, who may write, the read order, quotas, eviction, pins, and what release builds may read.
- Namespace: one slice of a tier:
{tier}/{ecosystem}/{scope}/{toolchain}, for examplebuild/go/acme/go1.27-linux-amd64. - Writer class: what a write is proven to come from (
protected-ci,prebuild,pr,fork,agent,box,serverless,release). - Overlay: a private namespace for one pull request, agent session or devbox. Nobody else reads it, and it is never promoted.
- Warm disk: a copy-on-write volume per repository and branch family; each commit of it is a generation.
Get started
Section titled “Get started”Runner pools, devboxes, agent sandboxes and serverless builds each name a cache policy. Frazer then sets up each job or box to use the cache, with no change to your build scripts.
-
Look at your site’s cache and its policy.
Terminal window frazer cache get ovh-vint-hillfrazer cache policies get acme-default -o yamlThe policy is also on the console, under Infrastructure › Build › Cache in the host project.
-
Point each consumer at the policy.
Consumer Where you set the policy Runner pool --cache-policyonfrazer githubrunners pools createDevboxes The devbox policy’s caches.policyAgent sandboxes The workspace template’s cachePolicyServerless builds The project setting build.cachePolicy -
Check what a writer may do before you rely on it. This asks what a pull request in
acme/storefrontmay read and write:Terminal window frazer cache policies test acme-default --as oidc:acme/storefront@refs/pull/611/merge -
Run a few jobs, then open the cache page or the job’s Cache tab. Each restore shows the tier that answered, the bytes and the seconds.
A policy, as frazer cache policies get acme-default -o yaml prints it:
ecosystems: [go, docker, actions-cache, pnpm, uv, turborepo]writers: shared: [protected-ci, prebuild] protectedCi: { refs: [main, "release/*", "v*"], workflows: [".github/workflows/ci.yaml", ".github/workflows/release.yaml"] } overlays: { pr: 2GiB, session: 5GiB, box: scratch, serverless: project } forks: none teamTier: offscope: { fallback: [overlay, branch, base, default], orgToolchainNamespaces: [go, bazel, sccache] }release: { readsBuildOutputs: false }quotas: { site: { build: 2TiB, warmDisks: 1TiB }, perProject: 1.5TiB, perNamespace: 200GiB }gc: { build: { lru: true, maxUnused: 14d }, deps: { maxUnused: 30d }, overlays: { afterClose: 7d }, warmDisks: { idle: 7d, generations: 3 } }pins: [warm-disk checkout-api@main, warm-disk web@main, build/go/acme/go1.27-linux-amd64]Use it from your tools
Section titled “Use it from your tools”Each ecosystem speaks its own protocol to the cache, so your build tools need no plugin.
| Ecosystem | How a job or box uses it |
|---|---|
actions/cache, setup-* actions, Docker type=gha |
A cache endpoint inside the runner VM answers GitHub’s cache calls and passes every other call (artifacts, logs) to GitHub unchanged. No workflow edits, and GitHub’s 10 GB repository limit does not apply. |
| Go | GOPROXY points at the site’s proxy, and GOCACHEPROG=frazer-cacheprog shares build outputs (Go 1.24 or later). |
| Docker and BuildKit | BuildKit state lives on the warm disk, so RUN --mount=type=cache persists. Main publishes a type=registry cache to a Frazer Registry cache repository with mode=max. |
| pnpm, npm, yarn | The store lives on the warm disk with node_modules; packages come through the dependency tier. |
| uv, pip | UV_CACHE_DIR lives on the warm disk; packages come through the dependency tier. |
| Bazel, Buck2, Pants | A Remote Execution API cache at the site, behind the cache’s own authentication. Jobs that may not write run with --remote_upload_local_results=false. |
| Turborepo, Nx | The cache speaks Turborepo’s and Nx’s remote cache APIs, with signatures on. |
| Gradle, Maven | An HTTP build cache at the site; only writers push. |
| Rust, C and C++ | sccache over WebDAV to the site’s store. |
| Tests | Go and Bazel test results ride the build tier. Flaky marks and test timings live in runner insights. |
In a runner job, the variables come preset:
GOPROXY=https://cache.<site>.internal/go,directGOCACHEPROG=frazer-cacheprogACTIONS_CACHE_MODE=read # for pull requests, agents and forksWho may write what
Section titled “Who may write what”Only trusted builds write what everyone else reads.
| Writer class | Proven by | May write | Reads |
|---|---|---|---|
protected-ci |
The CI provider’s OIDC token: repository, a protected ref, and a workflow on the policy’s allow-list | Its repository’s shared namespaces, the organization toolchain namespace, warm-disk commits | Everything the policy allows |
prebuild |
The Frazer identity of the prebuild worker | Shared namespaces, module snapshots, warm-disk generations | Everything |
pr |
OIDC event=pull_request from the same repository |
Its pull request’s overlay only | Overlay, branch, base, default branch |
fork |
OIDC head repository differs from the repository | Nothing | Dependencies; default-branch build outputs of public repositories |
agent |
A Frazer Agents identity, or a pull request whose author is a bot or on your agent list | Its session’s or pull request’s overlay | Same as pr |
box |
The devbox’s workload certificate | Its own box tier on scratch; a team namespace when the team tier is on | Shared, and its team’s namespace |
serverless |
The project’s build identity | The project’s namespace; shared only for protected branches | Dependencies and the project’s namespace |
release |
OIDC tag on a protected ref | Shared outputs, for main to reuse | Dependencies only |
- Stage, then publish on success. A job’s writes wait on the server and are published only when the job’s conclusion is
success. - Overlays are never promoted. When a pull request merges, main’s CI rebuilds and publishes. Overlays expire 7 days after the pull request closes or the session ends.
actions/cachefollows the same rule. A job that may not write getsACTIONS_CACHE_MODE=read, and the endpoint enforces it anyway.- Release builds read dependencies only, so nothing an untrusted job could have influenced reaches a release.
Every publish and every refusal is a row in the cache’s Activity, with the writer class, identity, namespace and bytes.
frazer cache writes --refused --since 24hPurge a bad entry
Section titled “Purge a bad entry”Every shared entry records who wrote it. If one turns out to be bad, purge everything that writer published, or a whole namespace. The plan lists the jobs that read the affected entries.
frazer cache purge --writer run:acme/storefront/123456 --dry-runWarm disks
Section titled “Warm disks”A warm disk holds what a repository’s jobs need before their first step: Git objects, toolchains, package stores and build state. Each job gets a copy-on-write clone; only clean default-branch jobs and prebuilds commit a new generation back. Pull request clones are thrown away.
frazer cache volumes list --repo acme/storefrontThe scheduler prefers the server that holds a repository’s newest generation. When that server is full, the job takes a clone over the network where the site has shared storage, or fills a fresh volume from the site’s store, which is slower and shows in the job’s timeline. Devboxes, agent sessions and runners of the same repository can start from the same generation.
Quotas, eviction and pins
Section titled “Quotas, eviction and pins”Quotas apply per site and tier, per consumer project (its share of the cache), per policy, per namespace and per overlay. Eviction runs by age and least recent use on every tier, and by disk pressure on every server.
Pins keep named namespaces and warm disks from eviction, such as release toolchains or main of your key repositories. Pins count against quota.
A nightly hygiene job removes orphaned blobs, flattens clone chains, samples digests, scans new archive entries for secret patterns and compacts storage. You can also run it from the console.
Access and audit
Section titled “Access and audit”- Per-job tokens: the cache mints a token per job from its OIDC token, scoped to namespaces and to read or write, valid only for the job’s lifetime. Eviction happens by policy, never because a client floods the quota.
- Signed entries: each action-to-result entry carries its writer class and identity and is signed with your organization’s cache key. Readers accept only the writer classes their policy allows. Blobs are content-addressed and verified on read.
- Encryption and secrets: overlays and shared namespaces are encrypted at rest per organization. An entry that matches a known secret pattern is quarantined and becomes a Security Center finding.
- Sharing: sharing a cache with another project, with a byte limit for that project, needs the
cache.caches.sharepermission. Cache keys carry the consumer project. - Audit: every publish, refusal and purge is on the cache’s Activity.
- Alerts:
FrazerCacheByteHitRateDrop(byte hit rate down 20 points in 15 minutes),FrazerCacheTierDown,FrazerCachePressureandFrazerCacheQuota90.
Limits and quotas
Section titled “Limits and quotas”| Setting | Default |
|---|---|
| Namespace quota | 200 GiB |
| Pull request overlay | 2 GiB |
| Agent session overlay | 5 GiB |
| Dependencies | Evicted after 30 days unused |
| Build outputs | Least recently used first; evicted after 14 days unused |
| Overlays | Deleted 7 days after the pull request closes or the session ends |
| Warm disks | Deleted after 7 idle days; 3 generations kept; clone chains flattened at depth 4 |
| Node hot tier | Evicts at 80 % disk use, aggressively at 90 %, stops admitting at 95 % |
| Slow or failing cache | Treated as a miss within 2 s |
| Scope | One site; a cache never spans sites and is never shared between organizations |
Pricing
Section titled “Pricing”On your own servers the cache uses your disks and is part of the per-core server fee. On the Frazer fleet you pay for stored GiB-months, with no request fee and no egress fee inside a site. Reads across sites are charged at the network rate, shown on the cache page.
| Cache | On your servers | On the Frazer fleet |
|---|---|---|
| Price | Included · uses your disks | $0.012 / GiB-month · no request fee |
Every line, with the estimator: pricing.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| A job’s writes never show up for other jobs | The job’s writer class may write only its overlay (pull requests, agents), nothing (forks), or the job did not finish with success. |
Check frazer cache writes --refused, and frazer cache policies test with the job’s identity. Shared entries come from protected-branch CI and prebuilds. |
| Main’s CI cannot write shared entries | The branch is not protected, or the workflow is not on the policy’s protectedCi allow-list. |
Protect the branch, or add the workflow file to writers.protectedCi.workflows. |
| Misses right after a toolchain or image change | Keys include the toolchain, the OS and architecture, and the runner image digest, so a hit must be exact. | Expected. The next protected-branch run publishes new entries. Top misses on the cache page names the reason. |
| Jobs are slower and the console shows a degraded banner | A cache tier is down or slow. Clients treat it as a miss within 2 s. | Builds keep running. The banner shows the measured impact; FrazerCacheTierDown fires. |
| A job’s timeline shows a long warm-disk step | The server holding the warm disk was full, so the job filled a fresh volume from the site’s store. | Nothing to fix for one job. If it happens often, add servers to the pool. |
| Entries you need are evicted | The namespace, project or site quota is full, or a server is under disk pressure. | Raise the quota in the policy, pin what must stay, and watch FrazerCacheQuota90 and FrazerCachePressure. |
| A bad entry was published | A trusted writer published it. | frazer cache purge --writer <identity> --dry-run, then re-run the affected jobs the plan lists. |
Reference
Section titled “Reference”CLI
| Command | What it does |
|---|---|
frazer cache get | Show a cache's tiers, usage and byte hit rate (bytes served ÷ requested). |
frazer cache namespaces list | List a cache's namespaces, filtered by tier and sorted by byte hit rate or size. |
frazer cache policies get | Show a cache policy; with -o yaml, its writers, scope, quotas and eviction rules. |
frazer cache policies test | Show what a given writer may read and write under a policy. |
frazer cache volumes list | List the warm disks of a repository. |
frazer cache purge | Remove entries by writer identity or by namespace, as a plan that lists the affected jobs. |
frazer cache writes | List published and refused writes; with --refused, refusals only. |
All of them: CLI reference.
MCP tools
| Tool | What it does |
|---|---|
frazer.cache.v1 | Read caches, namespaces and policies, test what a writer may do, and plan a purge. Writes become a plan that waits for approval. |
All of them: MCP tools reference.
Related
Section titled “Related”- GitHub runners: GitHub Actions jobs in microVMs next to the cache.
- Devboxes: boxes that read the same cache.
- Serverless: source builds that read and write a project namespace.
- Registry: where your builds’ registry cache and your images live.
- GitHub runners and Cache product page