CI and build platforms
Mount the cache. Skip the tarball.
Your remote build cache handles task outputs. Put package caches, toolchains, and a repository mirror on one Archil disk: jobs mount it read-only in 50 ms, and one refresh job on main writes it.
Each job downloads and extracts the whole archive, and uploads a new one when the key changes.
Mounts on your runners
VMs, FUSE containers, and Kubernetes pods via the CSI driver; an IAM role on AWS, a token anywhere. Mounting on Linux.
Everything your build cache doesn't hold.
A job that needs 200 MB of a 20 GB cache archive still downloads and unpacks all 20 GB.
Archives restored whole
actions/cache downloads and extracts the whole archive before the first step.
Per-host NVMe copies
A job lands warm on a host with a copy, and cold anywhere else.
Shared file systems sized for everything
FSx for Lustre starts at 1.2 TiB, and EFS bills every byte you keep.
Golden images restore cold
A restored volume pulls each block from S3 on its first read.
Stop tarring up your cache.
The job's first step becomes a mount.
Warm on a runner that just booted
Jobs that open the same toolchain at once share one S3 fetch; after that, any runner in the region reads it from Archil's SSD cache.
No save and restore step
Each job mounts the disk in 50 ms at the median.
Read what the job opens
A job reads only what it opens, and archil prefetch warms a directory it knows it needs.
Your remote cache holds outputs. The disk holds the rest.
Keep task outputs in your remote cache, and put dependencies, layers, toolchains, and the repository mirror on the disk. Most package managers can read a cache they can't write.
# Every job: the shared cache, read-onlysudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount \ dsk-XXXXXXXXXXXX /mnt/cache \ --region aws-us-east-1 --read-only # Gradle: a read-only dependency cache; misses go# to the build's own. Outputs: your remote cacheexport GRADLE_RO_DEP_CACHE=/mnt/cache/gradle-ro./gradlew build --build-cache # Go: modules from the disk, then the networkGOMODS=file:///mnt/cache/gomod/cache/downloadexport GOPROXY="$GOMODS,https://proxy.golang.org" # BuildKit: layers from the disk. (The GitHub Actions# backend is rate limited and skips cache mounts.)docker buildx build . \ --cache-from type=local,src=/mnt/cache/buildxOutputs in one place, inputs in another
Bazel, Turborepo, Nx, and Gradle key outputs by their inputs and expect dependencies on the runner.
Main writes the cache. Everything else reads it.
Only main's refresh job writes the cache, so a pull request can't change what your release builds from. Every other job mounts it read-only.
Sharing disks# Your orchestrator runs this when the lockfile# changes on main. It's the only job that writes.sudo archil mount dsk-XXXXXXXXXXXX /mnt/cache \ --region aws-us-east-1 --conditional # Fetch into the mirror; never let git collect itgit -C /mnt/cache/repo.git -c gc.auto=0 \ -c maintenance.auto=false fetch origin # Add main's packages to the shared npm cachesrc="$(mktemp -d)/src"git clone -q --reference /mnt/cache/repo.git "$REPO_URL" "$src"cd "$src" && npm ci --ignore-scripts --cache /mnt/cache/npm # Make the writes durable, then record this cachesudo archil sync /mnt/cachearchil checkpoints create /mnt/cache "cache-$(date +%Y%m%d%H%M)"sudo archil unmount /mnt/cache# Every other job, on any runnerexport ARCHIL_MOUNT_TOKEN="$JOB_TOKEN" # read-only tokensudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount \ dsk-XXXXXXXXXXXX /mnt/cache \ --region aws-us-east-1 --read-only # Clone onto the runner, borrowing objects from the mirrorgit clone --reference /mnt/cache/repo.git "$REPO_URL" srccd src && git checkout --force "$SHA" # Install from the shared cache; if this PR changed# the lockfile, install from the registry as todaynpm ci --offline --cache /mnt/cache/npm || npm ci npx turbo run build test # outputs: your remote cachesudo archil unmount /mnt/cacheA checkpoint per refresh, a branch to roll back
Each refresh ends with an immutable checkpoint in 13 ms; if one breaks builds, mount a branch of the last good one.
Checkpoints and branches (managed disks, AWS) can't be deleted yet; keep one per refresh and per rollback. Branches and checkpoints.
Put your cache volumes on Archil.
Back each customer's cache with its own disk, not a copy per host: stored once, read from any host.
- A disk per customer is one API call: 25,000 per region on Team, unlimited on Enterprise.
- Mount tokens are scoped to one disk; Archil stores only a hash.
- BYOC runs Archil, managed by Archil, in your cloud, your customers', or on bare metal; disk data never leaves.
- Data touched in the last hour bills at $0.20 per GiB-month; the rest of a managed disk, about $0.027.
// In your orchestrator, before each job. The API// key stays here; the runner gets only a token.import { Archil } from "disk" const archil = new Archil({ apiKey: process.env.ARCHIL_API_KEY, region: "aws-us-east-1",})const cache = await archil.getDisk(customer.cacheDiskId) // One mount, gone in 2 hours: outlast your longest// job, or the runner's reads and writes fail.// Only the refresh job on main writes; forks read.const { token } = await cache.addUser({ type: "token", nickname: `job-${job.id}`, ttl: "2h", oneUse: true, readOnly: job.kind !== "refresh",}) // The runner mounts with ARCHIL_MOUNT_TOKEN=token.// Terraform's archil_disk_user takes the same options.Time before the first step.
Your cache restore time and job count, against a 50 ms mount.
Your pipeline
Save and restore
5.6 hof runner time a day before first steps
- Each job waits
- 20 s
- Jobs a day
- 1,000
One mounted cache
50 sof runner time a day before first steps
- Each job mounts, median
- 50 ms
- Jobs a day
- 1,000
A model, not a benchmark: it counts only the wait before the first step. Saving a cache key and refreshing the disk happen only when the lockfile changes, so neither counts.
Five ways to cache a build.
If your jobs always land on a warm NVMe cache, keep it. Archil holds what a remote build cache doesn't.
| Property | Save and restore (actions/cache) | Per-host cache volumes | Remote build cache (Bazel, Turborepo, Nx) | Shared file system (EFS, FSx for Lustre) | Archil |
|---|---|---|---|---|---|
| Before the first step | Download, extract everything | Attach a nearby copy | Nothing; outputs fetched on hits | Mount | Mount, 50 ms median |
| Reads, once warm | Local disk | Local NVMe: fastest | Local disk | NFS or Lustre | Memory, then shared SSD |
| Many jobs at once | A copy each | Private copies; last commit wins | Safe: keyed by inputs | One live tree | Jobs read; refresh job writes |
| Retention | 7 days unused; 10 GB per repo | 7 to 14 days unused | Your server's policy | Until deleted | Until deleted; idle bills as archive |
| Rolling back a bad cache | Save a new key | The vendor's tools | Not needed | Your backups | Branch the last good checkpoint |
Save and restore (actions/cache)
- Before the first step
- Download, extract everything
- Reads, once warm
- Local disk
- Many jobs at once
- A copy each
- Retention
- 7 days unused; 10 GB per repo
- Rolling back a bad cache
- Save a new key
Per-host cache volumes
- Before the first step
- Attach a nearby copy
- Reads, once warm
- Local NVMe: fastest
- Many jobs at once
- Private copies; last commit wins
- Retention
- 7 to 14 days unused
- Rolling back a bad cache
- The vendor's tools
Remote build cache (Bazel, Turborepo, Nx)
- Before the first step
- Nothing; outputs fetched on hits
- Reads, once warm
- Local disk
- Many jobs at once
- Safe: keyed by inputs
- Retention
- Your server's policy
- Rolling back a bad cache
- Not needed
Shared file system (EFS, FSx for Lustre)
- Before the first step
- Mount
- Reads, once warm
- NFS or Lustre
- Many jobs at once
- One live tree
- Retention
- Until deleted
- Rolling back a bad cache
- Your backups
Archil
- Before the first step
- Mount, 50 ms median
- Reads, once warm
- Memory, then shared SSD
- Many jobs at once
- Jobs read; refresh job writes
- Retention
- Until deleted; idle bills as archive
- Rolling back a bad cache
- Branch the last good checkpoint