Skip to main content
Every remote build runs in a clean, single-use VM. Without caches, that means a full dependency install and a cold compile every time. Caches persist the directories you choose between builds, so the second build is much faster than the first.

Configuring caches

Declare caches under build.platforms.<key>.caches. Each cache has a key and a list of project-relative paths stored under it:
Paths must be project-relative and stay inside the workspace. Absolute paths and .. segments are rejected.

How it works

Each cache key maps to a dedicated cache disk that Revyl stores between builds:
  1. When a build starts, the disk for each configured key is fetched and attached to the build VM. A key that has never been saved gets a fresh, empty disk (20 GB).
  2. Before your commands run, each configured path is linked onto its cache disk. Whatever your build writes there, like node_modules, Pods, or DerivedData, lands on the disk.
  3. If the build succeeds, the disks are compressed and stored for the next build. Failed builds don’t update caches, so a broken run can’t poison them.
Because a cache is a real disk rather than a copied-in archive, restoring a populated cache doesn’t scale with file count. A node_modules tree with hundreds of thousands of files costs the same to attach as an empty one.

Scope and sharing

Cache keys are scoped to your organization. Any build in your org that names the same key gets the same cache across apps, platform streams, branches, and whoever (or whatever CI job) triggered the build. Use distinct keys when contents must not mix, e.g. ios-deps vs android-gradle.

Concurrency

Concurrent builds using the same key each get a private copy of the disk; they never see each other’s in-flight writes. On success, the last build to finish wins, the same semantics as GitHub Actions caches. There’s no locking, and there doesn’t need to be: a lost cache write only costs the next build some warm-up time.

Skipping caches

Runs a single build without restoring or saving caches. Use it to reproduce a cold build, or to rebuild past a corrupted cache. The next regular build starts from the last saved state, so one --no-cache run doesn’t reset anything. To actually discard a cache, change the key.

What to cache

Good candidates are directories that are expensive to recreate and safe to reuse:
  • JS dependencies: node_modules
  • CocoaPods: ios/Pods
  • Xcode DerivedData: the -derivedDataPath directory (e.g. build), for incremental compiles
  • Gradle project state: android/.gradle
Two caveats:
  • Only project-relative paths can be cached. Tool caches that default to the home directory (like Gradle’s ~/.gradle dependency cache) need to be pointed into the project first. For example, use GRADLE_USER_HOME=$PWD/.gradle-home ./gradlew assembleRelease with .gradle-home in paths.
  • If you cache the directory your artifact is built into (DerivedData), keep output specific enough that a stale product from a previous build can’t match the glob.