Cloud Mac File Sync in Practice: Using Mutagen for Remote iOS Projects

Remote Mac ·~6 min read

Cloud Mac File Sync in Practice: Using Mutagen for Remote iOS Projects

The repo is nearly 40GB, and Pods, node_modules, and DerivedData combined outweigh the actual source code. You open Xcode locally, with the project mounted on an SMB share hosted on a remote cloud Mac, and indexing takes over a minute — switching branches stalls long enough that you start wondering if your network dropped. This is the first wall most people hit after moving their dev environment to a cloud Mac. Mounting a shared volume looks like the easy option, but in practice you quickly discover that large iOS projects and network filesystems are fundamentally at odds in how they access files. This post walks through how we used Mutagen to set up bidirectional sync between local and remote on an HireVPS node, bringing the experience back to something close to native.

Why mounting a shared volume doesn't work for large iOS projects

Network filesystems like NFS and SMB are designed around the assumption of "occasional access to large files." Xcode indexing, SourceKit, and CocoaPods cache scanning do the exact opposite: they stat, open, and close tens of thousands of small files in a short burst. Every single operation requires a network round trip — even at 20ms latency, multiply that by tens of thousands of calls and you're looking at minutes of lag. Worse, connection handling is fragile: the mount point can freeze entirely when the connection hiccups, taking down both your terminal and editor with it, leaving you no choice but to force-unmount and remount.

A side-by-side comparison of three common approaches makes this clearer:

Approach Real-time behavior Handling many small files Recovery from disconnects Best suited for
SMB/NFS mount Strong (always current) Poor — per-file network round trips Poor — prone to freezing Occasional reads/writes of large files
Scheduled rsync Weak — sync gaps exist Decent — efficient batch transfers Good — just rerun the job Periodic backups, one-way pushes
Mutagen bidirectional sync Near real-time Good — local caching + incremental transfer Good — auto-reconnect and resume Ongoing bidirectional collaboration

One lesson we learned the hard way: we initially took the shortcut of just mounting the share, and ended up with Xcode's "Building workspace" step taking an extra 15-25 seconds on average. Multiply that across the team's daily indexing waits and it adds up fast. After switching to Mutagen, that wait essentially disappeared.

Setting up a bidirectional sync session with Mutagen

Installation and initial connection

Mutagen ships as a single binary and doesn't require any persistent background service on either the local machine or the cloud Mac — it automatically deploys an agent to the remote side when the connection is established. Install it locally first:

brew install mutagen-io/mutagen/mutagen
mutagen version

Assuming SSH is already enabled on your cloud Mac (credentials are in the provisioning email), create a sync session:

mutagen sync create \
  --name=ios-app \
  --ignore-vcs \
  --symlink-mode=posix-raw \
  /Users/me/Projects/ios-app \
  ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app

--ignore-vcs automatically skips internal .git objects (git's own protocol is a better fit for syncing itself — no need to have Mutagen duplicate that work), while --symlink-mode=posix-raw preserves the symbolic links commonly found in CocoaPods without converting them.

Ignore rules: directories that should never be synced

You should always create a .mutagenignore file at the project root — otherwise the very first sync will drag several gigabytes of cache directories along with it:

DerivedData/
Pods/
.build/
node_modules/
*.xcuserstate
xcuserdata/
.DS_Store

What these directories have in common is that they can be regenerated locally on either side. Syncing them wastes bandwidth and — as the next section explains — can also introduce real problems.

Common Xcode pitfalls

Real-world performance and conflict handling

In day-to-day use, an incremental sync triggered by a save (a handful of changed Swift files) typically completes within one or two seconds — indistinguishable from a local save. The first full sync (tens of thousands of source files) is noticeably slower since it has to scan the entire directory tree; it's worth running it once before you start working rather than waiting on it mid-edit.

For conflict handling, the default bidirectional mode is two-way-safe — it won't auto-overwrite on conflict, instead pausing and waiting for you to resolve it manually. That's safer than a force-overwrite approach, but if both sides are editing the same file, you'll hit frequent pauses. For team collaboration, we recommend:

mutagen sync create \
  --name=ios-app \
  --sync-mode=two-way-resolved \
  --default-file-mode-alpha=0644 \
  /Users/me/Projects/ios-app \
  ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app

two-way-resolved lets the local side (alpha) win by default on conflicts. Combined with a team convention of "only one person edits a given file at a time on one side," this rarely loses changes. If you do hit a conflict on binary assets (images, fonts), mutagen sync list will show the conflicting paths, and you can compare timestamps and file sizes manually to decide which version to keep.

A ready-to-use config

Mutagen supports project-level config files. Place one at the project root as mutagen.yml, and team members can spin up sync with a single command after cloning the repo — no need to type out all the flags by hand:

sync:
  ios-app:
    alpha: "."
    beta: "ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app"
    mode: "two-way-resolved"
    ignore:
      vcs: true
      paths:
        - "DerivedData"
        - "Pods"
        - "node_modules"
        - "xcuserdata"
        - ".build"
    symlink:
      mode: "posix-raw"

A handful of commands cover most day-to-day needs:

mutagen sync list
mutagen sync monitor ios-app
mutagen sync pause ios-app
mutagen sync resume ios-app
mutagen sync terminate ios-app

monitor is especially useful when you're trying to figure out why a change hasn't propagated — it shows in real time which stage (scanning, staging, or transferring) is currently the bottleneck.

Pre-launch checklist

Work through this list and your cloud Mac editing experience should feel close to native — and your team won't be pointing fingers over whose changes didn't make it across.

Frequently asked questions

Why not just mount the cloud Mac over SMB or NFS?

Network mounts turn every small file access (Xcode indexing, Pods lookups) into a round trip; large iOS projects can take tens of seconds just to open. Mutagen keeps a local cache and syncs incrementally, so editor responsiveness stays close to local speed and a dropped connection doesn't freeze your filesystem.

Should DerivedData be synced too?

No. DerivedData's module cache and indexes are tied to the machine's architecture and absolute paths; syncing it usually triggers a full re-index or build errors. Add it to .mutagenignore and let each machine regenerate its own.

What happens when a two-way sync conflicts?

Run mutagen sync list to find the conflicting paths. For most source-code conflicts, two-way-resolved mode with the local (alpha) side winning is enough; for binary assets, diff manually before deciding. Running git status before starting a sync session avoids most conflicts in the first place.

HireVPS

Try a dedicated cloud Mac mini today

Rent by the day, get SSH/VNC credentials in 2 minutes, and upgrade your configuration anytime.

Rent a Mac mini now