SSH into a cloud Mac from Windows or Linux
Start a macOS VM, sync your project, get a shell with Xcode on it, forward a port and pull the build back. A practical guide for WSL and Linux users.
· 7 min read
Need a Mac for this? NoMac gives your agent full macOS with Xcode, from $9.99 a month, billed by the second. Start now →
Most of what you need Xcode for is a command: xcodebuild, xcrun, simctl, swift test. If you are comfortable in a terminal, a remote desktop is mostly overhead. A shell on a Mac, with your project synced to it, is faster and far easier to script. This guide sets that up from Windows or Linux.
The shape of the workflow: edit locally in your own editor, sync to the Mac, run Apple tools over SSH, pull results back. Nothing about your local setup changes.
Prerequisites
Linux
Node 20 or newer, OpenSSH and rsync. Most distributions have the last two already.
# Debian or Ubuntu sudo apt install openssh-client rsync node --version # 20 or newer
Windows
Use WSL. The sync and pull commands rely on rsync, which is simplest inside a Linux distribution. Keep the project inside the WSL filesystem (for example ~/code/MyApp) rather than under /mnt/c; file operations are much faster there. VS Code's WSL extension edits it transparently. See VS Code and WSL for iOS.
Install and log in
npm install -g @nomac/cli nomac login
Approve the login in your browser. You need a NoMac plan with weekly hours; no Apple Developer account is needed for this guide.
Start a Mac and get a shell
cd ~/code/MyApp nomac start --json # ready in about a minute nomac sync . # upload the project nomac ssh # interactive shell on the Mac
You now have a macOS shell with root, Xcode and the command line tools. Check what you are working with:
xcodebuild -version xcrun simctl list runtimes sw_vers
Run single commands without a shell
For scripts and agents, run one command and return:
nomac ssh -- xcodebuild -list nomac ssh -- xcodebuild -scheme MyApp \ -destination 'generic/platform=iOS Simulator' build
Edit, sync, rebuild
Edit in your usual editor, then run nomac sync . again. It uploads changes, skips secret files and symlinks, and leaves extra remote files alone. That last point matters after a rename: the old file is still on the Mac. Delete it there explicitly, or Xcode may try to compile both.
Forward a port
If you run a local server on the Mac, for example a docs preview or a mock API your app talks to in the simulator, forward it to your machine:
nomac ssh --forward 8080:3000
Port 3000 on the Mac is now localhost:8080 on your machine for as long as the SSH connection is open.
Bring results back
nomac pull ./build --from build
Pull archives, test results, logs and screenshots before you stop. Use --overwrite only when you mean to replace local files.
Stop, and why a disconnect is not a stop
nomac stop --json
Closing your terminal or losing Wi-Fi does not stop the Mac, because long builds should survive a dropped connection. The idle timeout (ten minutes by default) and the session limit (two hours by default) eventually stop it, but billing runs until then. Make nomac stop a habit. Stopping deletes the VM and its files.
Tips for long work
- ·Run long builds inside
tmuxon the Mac (brew install tmuxif it is missing) so a dropped SSH connection does not kill them. - ·Script dependency setup (
brew bundle,pod install,swift package resolve) so every fresh session reaches the same state. - ·If you need symlinks, use plain
rsyncorscpover the same SSH connection instead ofnomac sync. - ·Check the session any time with
nomac session status --json: state, usage and deadlines.
Want your agent to do all of this instead? See give Claude Code a Mac.