Skip to content

Caching

RunnerHub includes automatic dependency caching to speed up build times. Caching is enabled by default and requires no manual configuration in your YAML.

RunnerHub automatically detects, restores, and caches dependencies based on lock files:

  1. Detect — Check for known lock files (Podfile.lock, package-lock.json, etc.)
  2. Restore — If cache exists with matching hash, download and restore to build directory
  3. Build — Your pipeline runs with cached dependencies
  4. Save — New dependencies are cached for future builds

This cycle happens transparently, without any configuration required.

RunnerHub automatically detects and caches these dependency managers:

ToolLock FileCache Key
CocoaPodsPodfile.lockSHA-256 hash of lock file
Swift Package ManagerPackage.resolvedSHA-256 hash of lock file
Bundler/GemsGemfile.lockSHA-256 hash of lock file
npmpackage-lock.jsonSHA-256 hash of lock file
Yarnyarn.lockSHA-256 hash of lock file
pnpmpnpm-lock.yamlSHA-256 hash of lock file
Requires npm install -g pnpm as the first step — pnpm is not pre-installed
DerivedData(auto-detected){platform}-v2-{branch}
Metro (React Native)(auto-detected){platform}-metro-{branch}

Each cache entry has a unique key based on the lock file or build artifacts:

For CocoaPods, SPM, Gems, npm, and Yarn, the cache key is the SHA-256 hash of the lock file content:

Podfile.lock (unchanged) → Same cache key → Cache hit → Restore cached pods
Podfile.lock (modified) → Different key → Cache miss → Download fresh pods

This means:

  • If Podfile.lock doesn’t change between builds, dependencies are instantly restored
  • If dependencies are updated (Podfile.lock changes), a fresh download occurs and new cache is saved

Xcode’s DerivedData is cached with a key based on platform and branch:

Key format: {platform}-v2-{branch}
Examples:
- ios-v2-main
- ios-v2-feature/auth
- macos-v2-develop

This allows different branches to maintain separate DerivedData caches, speeding up builds for feature branches without interfering with main branch builds.

Before your pipeline runs, RunnerHub checks for cached dependencies:

steps:
- name: Install pods
run: pod install # Uses cached pods if Podfile.lock unchanged

Timeline:

  1. Repository cloned
  2. Cache checked (lock file hash computed)
  3. If cache exists and matches → dependencies restored
  4. If no match → fresh download

After your pipeline completes successfully, new or updated dependencies are saved:

Build succeeds → New cache saved → Available for next build
Build fails → Cache NOT saved (only successful builds cache)

Cache is stored locally on the agent host machine, so subsequent builds on the same agent are nearly instant.

RunnerHub automatically removes old caches to manage disk space:

CriterionDetails
AgeCaches older than 14 days are deleted
SizeTotal cache size is limited to 50 GB per agent

When the 50 GB limit is reached, oldest caches are evicted first.

No manual cache configuration is needed in your YAML. RunnerHub automatically detects and caches everything:

# This pipeline caches everything automatically
name: My Pipeline
platform: ios
environment:
xcode: "16.4"
triggers:
- push
steps:
- name: Install dependencies
run: pod install # Automatically cached
- name: Install gems
run: bundle install # Automatically cached
- name: Build
run: fastlane build

If caching fails at any point, it never fails the build:

  • Cache restore error? → Fresh download continues, build succeeds
  • Cache save error? → Build completes normally, next build gets fresh dependencies

This ensures caching is purely an optimization—never a source of build failures.

To clear cached dependencies for your app:

  1. Open your app in the RunnerHub dashboard
  2. Navigate to App → Settings
  3. Find the Cache section
  4. Click Clear Cache

This removes all cached dependencies for your app, forcing the next build to download fresh packages. Useful for:

  • Troubleshooting cache-related issues
  • Forcing a clean build
  • Updating to the latest versions of cached dependencies

After clearing, the next build will:

  1. Download fresh dependencies
  2. Build normally
  3. Save new cache for subsequent builds

Scenario 1: Fast Builds with Stable Dependencies

Section titled “Scenario 1: Fast Builds with Stable Dependencies”

Your Podfile.lock doesn’t change for several commits:

Build 1: pod install (fresh) → 120 seconds
Build 2: pod install (cached) → 2 seconds (cache hit)
Build 3: pod install (cached) → 2 seconds (cache hit)
Build 4: Podfile.lock updated
pod install (fresh) → 120 seconds
Build 5: pod install (cached) → 2 seconds (cache hit)

Feature branch builds maintain separate DerivedData caches:

main branch: ios-v2-main cache
develop branch: ios-v2-develop cache
feature/auth: ios-v2-feature/auth cache

Switching between branches doesn’t interfere with each other’s caches.

A typical iOS project with multiple tools:

steps:
- name: Install gems (cached)
run: bundle install # Gemfile.lock → cache
- name: Install pods (cached)
run: pod install # Podfile.lock → cache
- name: Build (cached DerivedData)
run: xcodebuild build # Cached DerivedData

All three caches work independently and simultaneously.

Commit Lock Files

Always commit lock files to your repository so cache keys remain stable:

Terminal window
# Good: lock files committed
Podfile.lock committed
package-lock.json committed
yarn.lock committed
# Bad: lock files not committed
Podfile.lock gitignored
package-lock.json gitignored

Without committed lock files, cache keys change every build and caching becomes ineffective.

Update Dependencies Intentionally

When you want to update dependencies, update them locally, commit the new lock file, and push:

Terminal window
pod update
git add Podfile.lock
git commit -m "Update CocoaPods dependencies"
git push

The new lock file creates a new cache key, and builds use fresh versions.

Monorepo Considerations

In a monorepo with multiple Podfile.lock files or package-lock.json files, each is cached independently:

repo/
├── ios/
│ └── Podfile.lock → Cached separately
├── backend/
│ └── package-lock.json → Cached separately
└── shared/
└── Gemfile.lock → Cached separately

Cross-Platform Caching

Different platforms (iOS, macOS) have separate DerivedData caches:

ios-v2-main (iOS builds on main)
macos-v2-main (macOS builds on main)

Switching platforms doesn’t interfere with each other’s caches.

Check these common issues:

  1. Lock file not committed — Verify Podfile.lock (or other lock files) are in your repository
  2. Lock file path — RunnerHub looks for lock files in standard locations (repo root, directories)
  3. First build — The first build of an app won’t have cache; subsequent builds will
  4. Cache age — Caches older than 14 days are automatically deleted

To rebuild without cache:

  1. Click Clear Cache in your app’s dashboard
  2. Trigger the next build
  3. Next build uses fresh dependencies and creates new cache

Check your job logs for cache-related messages:

[cache] Checking for Podfile.lock...
[cache] SHA-256: a1b2c3d4e5f6...
[cache] Cache hit! Restoring...
[cache] Pods restored in 2s

or

[cache] No cache found for this hash
[cache] Downloading dependencies...
[cache] Pods installed in 120s
[cache] Saving new cache...
name: iOS Build
platform: ios
environment:
xcode: "16.4"
triggers:
- push
steps:
- name: Install pods
run: pod install # Cached via Podfile.lock
- name: Build
run: fastlane build # Uses cached DerivedData
name: macOS Build
platform: macos
environment:
xcode: "16.4"
triggers:
- push
steps:
- name: Resolve packages
run: swift package resolve # Cached via Package.resolved
- name: Build
run: swift build # Uses cached DerivedData
name: React Native Build
platform: react-native
environment:
node: lts
xcode: "16.4"
android_sdk: 34
triggers:
- push
jobs:
build-ios:
name: Build iOS
steps:
- name: Install dependencies
run: npm install # Cached via package-lock.json
- name: Install pods
run: cd ios && pod install # Cached via Podfile.lock
- name: Build iOS
run: xcodebuild -workspace App.xcworkspace -scheme App build
# Uses cached DerivedData
build-android:
name: Build Android
steps:
- name: Install dependencies
run: npm install # Cache hit (already installed)
- name: Build Android
run: cd android && ./gradlew assembleRelease
# Cached gradle wrapper

Caching behavior:

  • npm install in the iOS job creates and caches node_modules
  • npm install in the Android job hits the cache (same lock file)
  • Metro bundler cache at node_modules/.cache/metro is cached and restored per platform/branch
  • Both iOS and Android jobs benefit from the same dependency cache