2026 SwiftPM and CocoaPods on shared remote Mac CI Package.resolved · Bundler isolation · DerivedData disk governance

On a shared or dedicated remote Mac, running SwiftPM and CocoaPods together pits Package.resolved, Gemfile.lock, and DerivedData against disk quotas while concurrent jobs stomp the same paths. This article gives platform teams a sign-off checklist: seven hidden costs, a comparison table for SPM-only vs Pods-only vs mixed stacks, a six-step disk and cache runbook, and how to read it with our reproducible builds, self-hosted runner, and snapshots vs long-lived nodes guides.

01

Before you mix stacks: seven hidden costs that blow up lockfiles and disk on shared remote Mac CI

On Linux you split dependency caches and build outputs across volumes. On a shared macOS host, SwiftPM checkouts, the Pods tree, and DerivedData often default under one home directory; add parallel repos and disk or inode pressure hits before CPU. Use the seven items below as a pre-flight review.

  1. 01

    Package.resolved vs branch policy drift: parallel branches without per-branch buckets can resolve successfully yet link against the wrong artifacts.

  2. 02

    Ruby ecosystem drift for CocoaPods: mixing system Ruby, rbenv, and Bundler yields inconsistent pod versions—“green locally, flaky in CI.”

  3. 03

    Shared DerivedData roots: multiple jobs writing one -derivedDataPath stomp module caches and indexes; failures look like corrupt deps.

  4. 04

    Duplicate third parties across Pods and SPM: same library twice doubles download size and resolve time, often missed in architecture review.

  5. 05

    Cleanup scripts deleting live caches: cron jobs keyed only on age can remove artifacts for queued work and cascade failures.

  6. 06

    Inconsistent CDN / mirror policy: when pod repo update and SPM resolution use different egress paths, jitter becomes “CI is unstable.”

  7. 07

    No disk watermark contract: without a defined threshold to stop scheduling or go read-only, hosts fail full-disk before anyone notices—expensive to repair.

The shared root cause is importing the “single repo, single pipeline” mental model from Linux VPS into multi-tenant Mac land: you need namespaced caches and lockfiles, not just a bigger disk. Log these in your platform ledger, then use the next table to decide whether to converge stacks or accept mixed stacks with hard directory boundaries.

SwiftPM encodes repeatable resolution in Package.resolved with the compiler’s resolver; CocoaPods stays tied to Ruby and usually Bundler in enterprises. The worst pattern is letting engineers mutate global gems from an interactive shell on CI—injecting entropy into every shared host. Prefer a committed Gemfile.lock, bundle exec pod install only, and vendor/bundle on a partition you are allowed to wipe.

Pair with reproducible builds: fingerprint scripts should capture swift package resolve checksums and pod --version, not only Xcode. Pair with runners: if you mount caches on shared volumes, scope by repo subdirectory—not one giant DerivedData for everyone.

Network and CDN controls appear again in section 4 next to Bundler. Now the comparison table turns debates into partition rules.

02

SwiftPM-only, CocoaPods-only, or mixed: directory and risk trade-offs on shared remote Macs

There is no silver bullet: SPM-only keeps paths uniform but legacy ObjC often still needs Pods; Pods-only is familiar yet Ruby and CDN ops cost more. Write three SLAs into the review: resolution repeatability, disk peaks, and explainable failures.

DimensionSwiftPM-firstCocoaPods-firstMixed (common legacy)
Lockfiles & repeatabilityPackage.resolved tracks resolver/compiler couplingPodfile.lock + Ruby/Bundler must be pinnedTwo lockfiles and two cache trees—watch duplicate deps and path clashes
Disk profileCheckouts + SPM cache—predictablePods is large; Ruby toolchain adds overheadPeaks stack—bucket DerivedData explicitly
ConcurrencyEasy directory isolation; good for parallelismpod install can hold locks—queue itSplit job workdirs; never two writers on one path
Ops leversAligns with Xcode toolchain—fits fingerprint scriptsMaintain gem sources, CDN, Bundler cache policyLongest runbook, but most real-world apps land here
Golden imagesPreheat SPM cache layersOften bake Ruby + known Pods into baselinesImages grow—split toolchain / deps / mutable layers

Renting a Mac “like a VPS” at the dependency layer means buying predictable directory contracts and watermarks, not “hope the laptop disk survives.” Treat SPM and Pods caches as tenants, not background noise.

If you run an enterprise build pool, separate “dependency resolve” jobs from “signing and release” jobs: the former can tolerate aggressive cleanup; the latter must not share a home directory with experimental Bundler states. With snapshots vs long-lived nodes: mixed stacks on long-lived hosts need weekly cleanup plus watermarks; snapshot baselines benefit from layered preheat of Bundler and SPM caches.

When the decision is “cannot drop Pods yet,” encode rules: which third parties must stay SPM-only vs Pod-only (e.g., closed-source binaries), and forbid the same module through both channels or you pay at link time. Ship minimal Gemfile and .bundle/config snippets from a template repo so teams copy/paste instead of inventing per service.

03

Six steps to turn “SPM + Pods on a shared remote Mac” into a hand-off runbook

Order matters: partition first, resolve second, cache hits last. Align with reproducible builds fingerprints so dependency layers do not introduce a second undocumented environment.

  1. 01

    Fix a work root and branch bucket per repo: e.g. /ci/work/<repo>/<branch-hash>; keep SPM checkouts, Pods, and DerivedData under that bucket—never fall back to default ~/Library paths.

  2. 02

    Pin Bundler and CocoaPods: in the CI image or boot script use only bundle exec and point BUNDLE_PATH at vendor/bundle inside the bucket.

  3. 03

    Observe SwiftPM resolve separately from build: time resolve; on failure keep .build-adjacent logs so you do not confuse resolver errors with compile errors.

  4. 04

    Isolate DerivedData per job: always pass explicit -derivedDataPath to xcodebuild combined with the work root above.

  5. 05

    Define cleanup policy: trigger on watermark, run in idle windows, evict least-recently-used; never blanket rm -rf ~/Library.

  6. 06

    Wire runner labels: throttle heavy pod install jobs on their own label per the runner guide; do not starve compile-heavy jobs.

bash · work-bucket env example (tune per project)
export CI_WORK_ROOT="/ci/work/${REPO_SLUG}/${BRANCH_KEY}"
export DERIVED_DATA="${CI_WORK_ROOT}/DerivedData"
export PODS_ROOT="${CI_WORK_ROOT}/Pods"
export BUNDLE_PATH="${CI_WORK_ROOT}/vendor/bundle"
mkdir -p "$DERIVED_DATA"
bundle config set path "$BUNDLE_PATH"
bundle exec pod install --deployment
swift package resolve
info

Tip: if the same host also runs Fastlane releases, keep release buckets separate from CI resolve buckets so Bundler and App Store Connect tooling do not cross-contaminate.

On GitHub Actions and similar platforms, split “install deps” from “build/test” and key caches to lockfile contents, not only branch names. If a workspace mixes SPM and Pods, pin xcodebuild -workspace arguments so GUI habits do not diverge from CI forever.

With XCTest / Simulator: if tests reuse compile DerivedData, declare read vs write scope explicitly; otherwise test cleanup can nuke compile caches. Document “who may delete what” like you would for container volumes on Linux.

04

Bundler, CDN, and read-only fallback: keep network jitter out of “code is broken”

The most common CocoaPods production incident in enterprises is not a syntax error—it is resolve-time network failure misread as an application bug. Provide shared hosts with a single gem mirror policy and Git transport policy (HTTPS vs SSH), allow metadata hostnames at the firewall, and bake pod install timeouts and retries into workflows instead of defaults.

For SwiftPM, watch whether Package.resolved matches the resolver after major Xcode upgrades: run swift package resolve plus a minimal build on a canary job before opening the floodgates. Align with reproducible builds: any lockfile change should warn that shared resolve caches may need invalidation, not silent pollution.

warning

Warning: do not keep accepting jobs at the full-disk cliff—switch to read-only fallback (stop scheduling) and clean first; otherwise Xcode and git can half-write state that costs more than a short queue pause.

For multi-region fleets, chart CDN hit rate and resolve latency: when P95 spikes, suspect mirrors before asking teams to “retry three times.” Pair with SSH checklist: ship Bundler and pod versions via config management, not ad-hoc SSH upgrades.

05

Reference numbers you can paste into a design review

Tune thresholds to your parallelism and repo sizes; these are alignment anchors.

  • Shared system volume watermark: keep ≥20% free space; below it pause scheduling before deletes and log what you removed for audit.
  • Dual-stack peaks: on large iOS apps, DerivedData + Pods + SPM checkouts routinely reach tens of GB—size capacity as heaviest repo × concurrent jobs × safety factor, not single-build peaks.
  • Resolve repeatability probes: fingerprint scripts should record swift package resolve exit status, bundle exec pod --version, and a checksum of Podfile.lock as merge gates.

Laptops sleep and drift toolchains, which makes dependency issues feel mystical; pure Linux cannot host Apple’s official iOS stack. Moving resolve and build to a dedicated, always-on, partitionable remote Mac turns SwiftPM+CocoaPods into a contract. Compared to one-off hardware or flaky shared hosting, NodeMini’s Mac Mini cloud rental pairs fixed SSH, clear disk tiers, and repeatable node profiles—better for platform-grade dependency governance. Compare tiers in rental rates and onboard via the help center.

Tie this runbook to internal “dependency change levels”: lockfile patches vs minor vs major upgrades should carry different approvals and cache invalidation rules.

FAQ

FAQ

Not recommended. Split -derivedDataPath per repo/branch bucket and isolate Pods and Bundler vendor paths; otherwise concurrent jobs stomp caches. For platform guidance see the help center.

Ruby and Bundler versions, Gemfile.lock, and the CDN mirror policy for pod install; then set cleanup watermarks per shared host. Compare disk tiers in rental rates.

Reproducible builds covers Xcode fingerprints and Keychain; the runner article covers labels and cache mounts; this article covers directory and disk contracts when mixing SPM and Pods. Read all three for machine-to-dependency coverage.