Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Move a Hosted Mac Agent Without Losing Its State

Moving a hosted Mac agent is only risky in the seam between "old machine drained" and "new machine accepting work". The procedure below closes that seam with an explicit drain, an export that excludes secret values, a fresh runner registration, and a hold window for rollback.

Recommendation. If the agent is an Intel-compatible CI runner, API poller, or launchd-driven job, the Figment virtual Intel macOS plan at $29/month (2 vCPU, 8 GiB RAM, 80 GiB disk; browser-console access; VM capacity is being verified, so request early) is the cheapest valid target. If the job needs ARM execution, Apple-silicon-only binaries, Xcode 27, a GPU, or audio, request an M-series Mac starting at $95/month — the exact chip, RAM and disk are confirmed in your quote. Teams that want the CI setup, backups, monitoring and 30 minutes of monthly support handled for them should price the $249 Managed Build Mac. The i3 Mac at $49 and physical M-series plans collect requests until supply is confirmed; start the request a few days before your maintenance window.

Pick the right migration target

ScenarioPlanMonthly
Like-for-like Intel toolchain, launchd agents, scriptsVirtual Intel macOS$29
Physical Intel box for local I/O (request-based)i3 Mac$49
ARM execution, Xcode 27, GPU or audioM-series Macfrom $95
Delegated CI setup, backups, monitoring, 30 min supportManaged Build Mac$249
Two M-series + two runners, team setup, 1 h supportManaged Mac Team$999

The $29 VM runs Intel macOS on Linux/KVM, with no GPU acceleration and no audio. It can run compatible Intel Mac toolchains, including Xcode 26 or earlier on Intel, and it can cross-compile and sign supported arm64 device apps and universal macOS binaries. It cannot execute arm64-only binaries, Xcode 27, or visionOS tooling — pick M-series for those.

A real price check before you commit

If your job genuinely needs Apple silicon, compare the entry price honestly. CloudMac lists M4/16 GB/256 GB at $99.99/month, and cloudmac.org advertises a $999/year option — that is $83.25 effective monthly, roughly $141 less per year than twelve months of Figment's $95 M-series entry. Two caveats: Figment's $95 is an unspecified M-series starting price, not a declared M4/16 GB spec (the exact hardware is set in your quote), and CloudMac's 512 GB and 1 TB rows are currently marked "Fully reserved, Next allocation not scheduled". If Apple silicon is a nice-to-have and the Intel toolchain is fine, the $29 VM remains the cheapest valid target by a wide margin.

Inputs and required permissions

Treat the agent as three separable things and move each differently:

CategoryContentsTransfer method
Configurationlaunchd plists, scripts, cron entries, environment variable namesSource control or secure file transfer
SecretsAPI keys, tokens, signing identity, Developer ID private keysRetrieve fresh from your secret manager on the new host; rotate at source
StateJob state files, checkpoints, artifact cacheCopy explicitly and validate against a known-good reference

Never dump the live environment to a plaintext file — printenv output contains anything your shell has loaded. Export the list of variable names your agent requires, then re-inject values from your CI or password manager on the new host. Signing identities belong in an encrypted p12 (private key included) handled by your secrets system, not copied out of Keychain as bare files. Provisioning profiles are regular files on disk and should be re-downloaded from Apple Developer, not "exported" from Keychain.

You will need: admin on your CI service to issue a new runner registration token and remove the old runner; access to your secret manager to retrieve and rotate credentials; and permission to update your own firewall, artifact store or CI allowlists if any of them pin the old machine's egress IP.

The cutover, in five phases

1. Drain the old machine

touch ~/agent/.drain                 # your wrapper refuses new jobs if present
tail -F /tmp/agentjob.log            # watch in-flight work finish
launchctl list | grep com.yourteam.agentjob   # PID column shows "-" when idle

Mark the runner offline in your CI UI and tell upstream schedulers (cron orchestrators, webhook dispatchers) that this host is going away.

2. Export configuration, not secrets

#!/usr/bin/env bash
set -euo pipefail
umask 077
OUT="$HOME/agent-export-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$OUT"/{launchd,scripts,state,env-names}

cp ~/Library/LaunchAgents/com.yourteam.agentjob.plist "$OUT/launchd/"
cp /usr/local/bin/run-agent-job.sh /usr/local/bin/reconcile-job.sh "$OUT/scripts/"

# State you intentionally carry across
[ -f ~/agent/job.state ] && cp ~/agent/job.state "$OUT/state/"
[ -d ~/agent/checkpoint-latest ] && cp -R ~/agent/checkpoint-latest "$OUT/state/"

# NAMES ONLY — values stay in your secret manager
grep -oE '\$\{?[A-Z_][A-Z0-9_]*' /usr/local/bin/run-agent-job.sh \
  | tr -d '${' | sort -u > "$OUT/env-names/required.txt"

brew bundle dump --file="$OUT/Brewfile" --describe --force
echo "Wrote $OUT. Transfer over an encrypted channel."

This script is an illustrative example, not a benchmark — adapt variable discovery to how your scripts actually reference secrets.

3. Stand up the new machine, drained

mkdir -p ~/agent && touch ~/agent/.drain   # start blocked

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew bundle install --file=/path/to/agent-export/Brewfile

install -m 0755 /path/to/agent-export/scripts/*.sh /usr/local/bin/
cp /path/to/agent-export/launchd/*.plist ~/Library/LaunchAgents/

# Inject secrets from your manager — one per required.txt line
# e.g. export API_KEY="$(op read 'op://CI/agent/api-key')"

cp /path/to/agent-export/state/job.state ~/agent/

4. Re-register the CI runner

For a GitHub Actions self-hosted runner, follow the current docs on removing runners and hardening self-hosted runners.

On the old machine (or via the repository/organization Settings → Actions → Runners UI):

./config.sh remove --token OLD_REMOVAL_TOKEN

On the new machine, with a fresh registration token:

./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO \
            --token NEW_REGISTRATION_TOKEN \
            --name figment-mac-agent-v2 \
            --labels mac,automation
./svc.sh install && ./svc.sh start

GitHub automatically removes a persistent self-hosted runner that has not connected for more than 14 days, and an ephemeral runner after 1 day — so the explicit removal step above is a cleanup courtesy if you are inside those windows, and required if you are outside them.

5. Smoke test, cut over, hold

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.yourteam.agentjob.plist
shortcuts run "AgentSmokeTest"       # see support.apple.com shortcuts CLI guide
# Confirm runner shows Online in the CI UI, then:
rm -f ~/agent/.drain

Redirect upstream triggers (cron, webhooks, dispatcher config) to the new host. Keep the old machine drained but reachable for 24–48 hours for rollback, then decommission.

Acceptance checklist

EXPORT
  [ ] Drain flag set; in-flight job finished; log quiet
  [ ] CI runner shows Offline in service UI
  [ ] Upstream schedulers / webhook senders notified
  [ ] Export directory contains launchd, scripts, state, Brewfile, required.txt
  [ ] No secret values present (grep export dir for known key prefixes)

NEW MACHINE
  [ ] Homebrew installed; brew bundle check exits 0
  [ ] Scripts installed with mode 0755
  [ ] launchd plists present under ~/Library/LaunchAgents
  [ ] Secrets pulled fresh from secret manager AND rotated at source
  [ ] job.state restored and matches last good checkpoint

REGISTRATION
  [ ] Old runner removed (or within auto-removal window)
  [ ] New runner registered with fresh token and expected labels
  [ ] New runner shows Online before traffic shift

CUTOVER
  [ ] Smoke test passed end to end
  [ ] Drain flag removed on new machine
  [ ] Upstream triggers redirected
  [ ] Old machine drained, reachable, monitored 24–48 h

DECOMMISSION
  [ ] No rollback events during hold
  [ ] Old launchd agents bootout'd; service uninstalled
  [ ] Plan cancelled or repurposed with Figment

Failure modes and recovery

FailureSymptomRecovery
Missing secret on new hostAuth error on first real jobPull from secret manager; never copy from old host
Tool version driftUnexpected output, crashbrew list --versions <formula>; pin and reinstall
Runner stays offlineNo jobs dispatched after cutoverCheck ./svc.sh status and launchctl print gui/$(id -u)/...
State divergenceNew host replays or skips workCompare job.state with upstream; replay only verified clean checkpoints
Hard failure on new hostJobs failing within hold windowReinstate old machine: rm ~/agent/.drain, re-point triggers

Cost during the overlap

The $29 virtual Intel macOS plan is billed monthly — a two-day or two-week overlap still costs a full month on each host. Schedule the cutover near the start of a billing cycle so the second month buys the most useful validation time. For i3 and M-series plans the overlap terms sit in your quote; confirm them before you start the move. There is no advertised trial or pro-rated daily rate to lean on.

Start the migration

Request your new Figment Mac now and keep the old one running until the hold window closes.

Start a hosted Mac at Figment → · Browse Mac hosting plans →

Related guides:


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms