Sign Up

macOS Sandboxes

macOS virtual machine sandboxes, their capabilities, use cases, and architecture.

macOS sandboxes are macOS virtual machines (VMs) that run on Apple silicon Mac computers. Each sandbox is a virtual machine that runs its own copy of macOS on a Mac that your organization reserves, and gets a fixed half of that Mac's processor cores and memory. The hardware enforces the boundary and isolation between sandboxes.

A Mac is reserved by one organization at a time, for at least 24 hours, and runs at most two macOS sandboxes at once. These limits come from Apple's macOS license. A sandbox runs on a free slot of one of the organization's reservations, or on a new reservation bought for it.

TermDescription
ReservationA lease of one whole Mac to one organization for a paid period of at least 24 hours. Also called a Mac lease.
SlotRoom for one macOS sandbox on a reserved Mac. A reservation has one or two slots.
Auto-reserveAn organization setting that lets a macOS create or start buy a reservation when no reserved Mac has a free slot.
Base imageA prepared macOS disk that every macOS sandbox starts from.
CheckpointA saved copy of a persistent macOS sandbox's disk, written to object storage when the sandbox stops, that a start restores.
Virtualization frameworkApple's macOS framework for creating and running virtual machines on Apple silicon.

Use cases

macOS sandboxes are used when the software, the build, or the task requires macOS.

  • Build and test Apple platform software: build, sign, and test macOS and iOS applications with Xcode on a clean machine for each run. Every sandbox starts from a copy-on-write clone of the base image.
  • iOS simulator tests: the base image includes an iOS simulator runtime, so iOS applications run and are tested in the simulator inside the sandbox.
  • Computer-use agents on macOS: an agent operates macOS applications through screenshots, input events, and the accessibility tree.
  • macOS-only software: programs and command-line tools that run only on macOS run in an isolated machine instead of on a shared Mac.
  • Continuous integration on macOS: build and test jobs run on reserved Macs, with each job in a sandbox that starts from the same base image. Only your organization's sandboxes run on its reserved Macs.
  • Agents with the same API as Linux: the toolbox in a macOS sandbox serves the same API as the one in a Linux sandbox. SDK calls written for Linux sandboxes work against macOS sandboxes.
  • Work across days: a persistent sandbox writes a checkpoint of its disk on every stop and when its reservation ends. It starts again on any reservation of the organization in the region.
  • Remote macOS desktops: macOS desktops in the browser, with no local Mac. The Daytona proxy authenticates to Screen Sharing, so the browser never receives a Screen Sharing password.

Create a macOS sandbox

Create a macOS sandbox from the dashboard or programmatically. The sandbox needs a Mac reservation in its region: an existing one with a free slot, a new one bought for this sandbox, or one that auto-reserve buys. See Mac reservations.

  1. Go to Daytona Sandboxes ↗
  2. Click Create
  3. Select macOS sandbox
  4. Click Create
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create_macos("base-macos")

The following example sets the remaining parameters:

from daytona import Daytona
from daytona.v2 import PortRequest

daytona = Daytona()

sandbox = daytona.v2.create_macos(
    "base-macos",
    name="my-macos-sandbox",
    labels={"team": "qa", "run": "42"},
    ports=[PortRequest(vm_port=8080)],
    env={"LOG_LEVEL": "debug"},
    persistent=True,
    reservation="new",
    auto_renew=True,
    region="us",
    lifecycle={
        "idle_timeout_secs": 3600,
        "on_idle_timeout": "stop",
        "timeout_secs": 86400,
        "on_timeout": "delete",
    },
    idempotency_key="run-42-create",
    create_ttl=1800,
    wait=120,
)
ParameterTypeDescription
imagestrSets the macOS base image by name or ID, such as base-macos. This is the first positional argument and is required.
namestrSets the display name. If omitted, a name is generated.
labelsDict[str, str]Attaches key-value metadata to the sandbox.
portsSequence[PortRequest]Forwards inbound ports to the sandbox.
envDict[str, str]Sets environment variables inside the sandbox.
persistentboolMakes every stop write a checkpoint, which start restores on any Mac the organization reserves. This enables stop and start, and pause and resume. The default is False.
reservationstrSelects the reservation for the sandbox. "new" always buys a reservation for this sandbox. A reservation ID places the sandbox on that reservation. A reservation that the create buys stays bought and charged if the sandbox is deleted before the sandbox runs. By default, the sandbox takes a free slot on one of the organization's reservations in the region, or a reservation bought by auto-reserve.
auto_renewboolRenews the bought reservation near each period end while a sandbox is active on the reservation. It applies only with reservation="new". Passing auto_renew without reservation="new" raises before the request is sent. The default is the organization's auto-reserve setting.
regionstrSets the region slug. The default is the named reservation's region, or else the project default.
lifecycleLifecycleConfigSets the lifecycle policy with timeout_secs and on_timeout, and idle_timeout_secs and on_idle_timeout. Each action is stop or delete, and stop needs persistent. The policy does not accept the pause action or wake on request, and the project's lifecycle defaults do not apply. To pause the sandbox, call pause directly. By default, there is no idle timeout, and the wall-clock timeout uses the platform default, if one is enabled.
idempotency_keystrMakes a retried create return the same sandbox. A retry never buys a second reservation. By default, each call generates a new key.
create_ttlintSets how many seconds the sandbox has to reach running before the server deletes it. The time includes waiting for a Mac. The default is 3600.
waitintSets how many seconds create waits for the sandbox to start. 0 returns immediately. The default is 60.

Mac reservations

A reservation leases one whole Mac to one organization. Only the organization's sandboxes run on its reserved Macs, and the organization pays for each period whether or not sandboxes run. Sandbox usage on a reserved Mac is not billed separately.

Each reservation has a period of at least 24 hours and one or two slots. Period 1 starts when a Mac takes the reservation, so time spent waiting for a Mac is not paid. Every reservation is visible to every project of the organization. A reservation cannot be ended early.

A create or start without a named reservation runs on the reservation in the region with the most free slots. Sandboxes on a reservation are not tied to its Mac: a stopped persistent sandbox can start on any reservation of the organization in its region.

StatusDescription
pending_chargeWaiting for billing to accept the charge for the first period.
leasingPaid, and waiting for a Mac to take it. Fails, and the charge is reversed, if no Mac takes it in time.
activeHeld by a Mac. Sandboxes run on it.
endingThe period ended. Persistent sandboxes are stopped and checkpointed, and ephemeral sandboxes are deleted.
releasedEnded. The Mac is wiped before another organization can reserve it.
failedBilling refused the charge, or no Mac took the reservation.

pending_charge, leasing, and active reservations admit sandboxes. A sandbox admitted by a reservation that no Mac holds yet waits in pending until a Mac takes the reservation.

Reserve a Mac

Reserve a Mac in a region. The server charges the first period, and the reservation becomes active once a Mac in the region takes it. The dashboard lists reservations on the Mac leases ↗ page.

from daytona import Daytona

daytona = Daytona()
reservation = daytona.v2.reservations.create(
    region="us",
    vms_per_mac=2,
    auto_renew=True,
    idempotency_key="reserve-us-1",
)
ParameterTypeDescription
regionstrSets the slug of the region to reserve a Mac from. This parameter is required.
vms_per_macintSets how many macOS sandboxes the Mac runs at once, 1 or 2. The default is 2.
auto_renewboolRenews the reservation near each period end while a sandbox is active on the reservation. The default is the organization's auto-reserve setting.
idempotency_keystrMakes a retried reservation return the first reservation instead of buying another. By default, each call generates a new key.

List and get reservations

List the organization's reservations, newest first, or get one by ID. The list contains open reservations and those that ended in the last 30 days.

reservations = daytona.v2.reservations.list()
reservation = daytona.v2.reservations.get(reservations[0].id)
print(reservation.status, reservation.slots_used, reservation.period_end)
Response fieldDescription
idThe reservation ID. Pass it as reservation to create a sandbox on this reservation.
regionThe region of the reserved Mac.
statusOne of the reservation statuses.
originexplicit for a reservation bought directly or by a create with reservation: "new"; auto for one bought by auto-reserve.
auto_renewWhether the reservation renews itself near each period end.
vms_per_macThe number of slots.
slots_usedmacOS sandboxes currently on the reserved Mac.
period_nThe current paid period, starting at 1.
period_hoursThe period length in hours.
period_endWhen the reservation ends unless renewed. Unset until a Mac takes it.
renewal_pendingA renewal charge is waiting for billing.
renewal_errorWhy the last renewal failed. The reservation ends at period_end unless renewed by hand.
end_reasonWhy the reservation ended: expired, released, charge_failed, not_accepted, reissued, or operator.

Renew a reservation

Renew an active reservation by hand. The server charges the next period, and the reservation keeps the same Mac with period_end moved out by one period.

A reservation with auto-renew on renews itself shortly before period_end while a macOS sandbox is active on it. A reservation with no active sandbox lapses at period_end, and a paused sandbox does not count as active.

reservation = daytona.v2.reservations.get("{reservationId}")

# Renew for the next period
daytona.v2.reservations.renew(reservation.id)

# Turn auto-renew off and let the reservation lapse at period_end
daytona.v2.reservations.update(reservation.id, auto_renew=False)

Auto-reserve

Auto-reserve lets a macOS create or start buy a reservation when the organization has none in the region, or when all of its slots are in use. The bought reservation has a 24-hour period and auto-renew on. Auto-reserve is off by default, and only organization admins can turn it on.

The organization also sets the maximum number of live reservations auto-reserve may hold at once. The default is 2. A create that would exceed it fails with the code AUTO_RESERVE_LIMIT_REACHED.

Turn auto-reserve on in the organization settings of the Daytona Dashboard ↗, or through the API with a user token:

curl 'https://api.daytona.com/orgs/{organizationId}/macos-settings' \
  --request PATCH \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_USER_TOKEN' \
  --data '{ "macos_auto_reserve": true, "macos_auto_reserve_max_leases": 4 }'
FieldTypeDescription
macos_auto_reservebooleanTurns auto-reserve on or off. By default, the setting is unchanged.
macos_auto_reserve_max_leasesintegerSets how many live auto-reserved reservations are allowed at once. The minimum is 1. By default, the setting is unchanged.

Desktop and remote access

The macOS desktop is served by Apple Screen Sharing inside the sandbox. VNC access connects to it through a Daytona proxy, which authenticates to Screen Sharing on your behalf, so the browser never receives a Screen Sharing password.

SSH connects to the macOS built-in OpenSSH server through the same gateway as Linux.

Lifecycle

Every macOS sandbox moves through a lifecycle. A macOS sandbox can have several different states. Each state reflects the status of your sandbox. A macOS sandbox transitions between states in response to user actions, when its reservation ends, or automatically when one of its timeouts elapses.

FeaturePersistentEphemeral
Stop and start✓✗
Pause and resume✓✗
Fork✗✗
Delete✓✓
Filesystem persistence✓✗
Memory persistence✓✗
Memory snapshot✗✗
Filesystem snapshot✗✗
Spot (Preemption)✗✗
Network policy✗✗
Desktop✓ Screen Sharing✓ Screen Sharing

Every macOS sandbox supports wait for state, delete, timeouts, and lifecycle policy updates. Stop and start, pause and resume, and auto-stop require a persistent sandbox. macOS sandboxes do not support fork. A persistent macOS sandbox keeps its disk as a checkpoint across stop and start, so a start can run on any Mac the organization reserves in the region. It keeps its memory and running processes across pause and resume, on the Mac that paused it. An ephemeral macOS sandbox keeps its disk and memory until it is deleted or its reservation ends.

When a reservation ends, its Mac stops and checkpoints every persistent sandbox, including paused ones, and deletes every ephemeral sandbox. Persistent sandboxes stay stopped and can start on another reservation of the organization.

Sandbox states
StateDescription
queuedThe create request is recorded and waits to be published for placement.
pendingThe create request is accepted and the sandbox waits for a reserved Mac to take it.
assignedA reserved Mac takes the sandbox. A start or resume also re-enters this state.
bootingThe macOS virtual machine is booting and will be ready to use.
runningThe sandbox is running and ready to accept requests.
stoppingmacOS is shutting down and the sandbox no longer accepts requests.
stoppedThe sandbox is stopped. Its slot is freed and its disk is kept as a checkpoint in object storage.
pausingThe sandbox is pausing while its memory is saved on its Mac.
pausedThe sandbox is paused with disk, memory, and process state preserved on its Mac. It keeps its slot.
deletingThe sandbox is being deleted.
deletedThe sandbox and its disk are removed, and its slot is freed.
failedThe sandbox could not boot or hit an unrecoverable error.
lostThe Mac running an ephemeral sandbox was lost. A persistent sandbox on a lost Mac becomes stopped instead.
State transitions
Initial stateTarget stateTrigger
queuedpendingThe create request is published for placement.
pendingassignedA reserved Mac with a free slot takes the sandbox.
assignedbootingThe Mac clones the base image and the macOS virtual machine boots.
bootingrunningThe sandbox daemon answers and the sandbox accepts requests.
runningstoppingA stop is requested on a persistent sandbox.
pausedstoppingA stop is requested on a paused sandbox.
stoppingstoppedmacOS shuts down and the Mac uploads a checkpoint of the disk. Memory state is cleared.
stoppedassignedA start is requested and a reserved Mac in the region takes the sandbox.
assignedstoppedNo reserved Mac takes a start before its deadline, or a resumed sandbox cannot start on its Mac. The checkpoint is kept.
runningpausingA pause is requested on a persistent sandbox.
pausingpausedThe memory and running processes are saved on the Mac.
pausingrunningThe Mac cannot finish the pause, and the sandbox keeps running.
pausedassignedA resume is requested and the sandbox continues on the same Mac.
running or pausedstoppedThe reservation ends, and the Mac shuts down the persistent sandbox and checkpoints its disk.
runningdeletedThe reservation ends, and the Mac deletes the ephemeral sandbox.
any statedeletingA delete is requested.
deletingdeletedThe sandbox is removed and its slot is freed.
any active statefailedThe sandbox fails to boot or hits an unrecoverable error.
runningstoppedThe Mac running a persistent sandbox is lost.
pausedfailedThe Mac running a paused sandbox is lost and the sandbox has no committed checkpoint.
runninglostThe Mac running an ephemeral sandbox is lost.

Wait for state

Wait until a macOS sandbox reaches a target state. The wait stops early when the sandbox reaches a state from which the target is no longer reachable, such as failed or deleted. The SDKs use their own state names, so the SDK started state corresponds to the running state in the CLI and API.

# Block until the sandbox is started; returns the sandbox
sandbox.wait(until="started", timeout=60)

# Block until the sandbox is stopped or paused
sandbox.wait_for_state(["stopped", "paused"], timeout=60)
ParameterTypeDescription
untilstrSets the one state that wait() waits for. wait_for_state() takes a list and returns on any of its states. The default is "started".
timeoutfloatSets the wait limit in seconds. None or 0 waits without a limit. When the timeout elapses, both methods raise RequestTimeoutError. The default is 60 for wait() and None for wait_for_state().

Stop / start

Stop and start a persistent macOS sandbox. Stopping shuts macOS down, frees the sandbox's slot, and uploads a checkpoint of the disk. Memory state is cleared. While a stop is in progress, the sandbox is in the stopping state and does not accept new requests. Starting restores the latest committed checkpoint on any Mac the organization reserves in the region. A start is admitted like a create: it needs a free slot, and with auto-reserve on it can buy a reservation.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.get("{sandboxId}")

# Shut macOS down and upload a checkpoint of the disk
sandbox.stop()
sandbox.wait_for_state(["stopped"], timeout=60)

# Restore the latest checkpoint on a reserved Mac
sandbox.start()

Pause / resume

Pause and resume a persistent macOS sandbox. Pausing saves the sandbox's memory on its Mac and frees its processor and memory. The paused sandbox keeps its slot. While a pause is in progress, the sandbox is in the pausing state. Resuming restores memory on the same Mac and continues all processes from the point they were frozen. A paused sandbox cannot resume on another Mac.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.get("{sandboxId}")

# Save the sandbox's memory on its Mac; returns once the sandbox is paused
sandbox.pause()

# Restore it on the same Mac
sandbox.resume()
ParameterTypeDescription
timeoutfloatSets how many seconds pause() waits for paused and resume() waits for started. The default is 60.

Delete

Delete a macOS sandbox to tear down its virtual machine. Deletion works in any state, and the call returns once teardown starts. The sandbox moves from deleting to deleted when the teardown finishes. A stopped or paused sandbox moves to deleted at once. Daytona discards the sandbox's disk and memory, and removes the checkpoints of a persistent sandbox afterwards. Deleting a macOS sandbox frees its slot on the reserved Mac. It does not end the reservation.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.get("{sandboxId}")

sandbox.delete()

Timeouts

Daytona stops or deletes a macOS sandbox automatically when one of its two timers elapses. Each timer pairs an interval in seconds with the action it fires. Set both timers at create in the lifecycle object, and change them later through update lifecycle policy.

TimerPurposeFires afterDefault
TimeoutCap how long a sandbox runsThe sandbox has been running for the interval, active or notPlatform default
Idle timeoutStop or delete a sandbox that is not in useThe sandbox has had no activity for the intervalOff

The valid actions depend on whether the sandbox is persistent and apply to both timers. Delete is valid for every macOS sandbox:

ActionEphemeral (default)Persistent
Stop✗✓
Pause✗✗
Delete✓✓

Auto-stop and auto-delete are not separate settings. Each one is the idle timeout with the matching action. The lifecycle policy does not accept the pause action or wake on request. To pause a macOS sandbox, call pause directly.

Timeout

The timeout is a wall-clock budget. It starts when the sandbox is created and re-arms with the full budget every time the sandbox enters running: on start and resume. Activity does not extend it. A delete timeout fires in any state. A stop timeout fires only while the sandbox is running. Once set, the timeout cannot be cleared.

When the timeout is omitted at create, Daytona applies the platform default timeout of 300 seconds, if the platform default is enabled. The project's lifecycle defaults do not apply to macOS sandboxes. An explicit timeout must not exceed the organization maximum, 86,400 seconds (24 hours) unless the organization sets a different maximum.

When the action is omitted, it defaults to stop for persistent macOS sandboxes and delete for ephemeral macOS sandboxes. A stop writes a checkpoint of the disk.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create_macos(
    "base-macos",
    # Delete the sandbox 2 hours after it last entered running
    lifecycle={"timeout_secs": 7200, "on_timeout": "delete"},
)

Idle timeout

The idle timeout fires after the sandbox has had no activity for the interval. Activity is any request through the toolbox API, preview URLs, VNC, and SSH, and any lifecycle operation on the sandbox. Processes running inside the sandbox do not count as activity. The idle timeout is off by default, has a minimum of 60 seconds, and requires an action. An idle stop fires only while the sandbox is running. The action fires within about 2 minutes after the interval elapses.

Auto-stop

Stop a persistent macOS sandbox after a period of inactivity. The stop writes a checkpoint of the disk and frees the sandbox's slot.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create_macos(
    "base-macos",
    persistent=True,
    lifecycle={"idle_timeout_secs": 900, "on_idle_timeout": "stop"},
)
Auto-delete

Delete a macOS sandbox after a period of inactivity. Deleting the sandbox frees its slot.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create_macos(
    "base-macos",
    lifecycle={"idle_timeout_secs": 3600, "on_idle_timeout": "delete"},
)
Reset the idle timer

Reset the idle timer of a macOS sandbox to keep it alive while it does work that Daytona does not count as activity, such as a build that runs without requests to the sandbox. The reset counts as activity, so the idle countdown restarts from now. The timeout is not affected.

The reset succeeds in any state, whether or not an idle timeout is set. It never moves the timer backwards: when newer activity arrives at the same time, the newer activity wins.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create_macos(
    "base-macos",
    lifecycle={"idle_timeout_secs": 3600, "on_idle_timeout": "delete"},
)

lifecycle = sandbox.reset_idle_timer()
print(lifecycle.idle_timeout_at)

Update lifecycle policy

Update the timeouts of an existing macOS sandbox. Omitted fields keep their current values. The new actions must be valid for the sandbox, as listed in timeouts.

from daytona import Daytona
from daytona.v2 import DISABLED

daytona = Daytona()
sandbox = daytona.v2.get("{sandboxId}")

# Stop the sandbox after 30 minutes without activity; omitted fields keep their current value
lifecycle = sandbox.set_lifecycle(idle_timeout_secs=1800, on_idle_timeout="stop")
print(lifecycle.idle_timeout_secs, lifecycle.on_idle_timeout)

# Re-anchor the timeout to 1 hour from now
lifecycle = sandbox.set_lifecycle(timeout_secs=3600)
print(lifecycle.timeout_at)

# Clear the idle timeout
lifecycle = sandbox.set_lifecycle(idle_timeout_secs=DISABLED)

Filesystem persistence

Filesystem persistence keeps the contents of a sandbox's disk: installed tools, cloned repositories, build artifacts, and the files the sandbox writes. A sandbox with a preserved disk starts with its environment intact, with nothing to reinstall or rebuild.

macOS sandboxes are ephemeral by default. An ephemeral macOS sandbox keeps its disk on its Mac. Deleting the sandbox discards the disk, and so does the end of its reservation, which deletes every ephemeral sandbox on the Mac. A persistent macOS sandbox writes a checkpoint of its disk to object storage on every stop and when its reservation ends. A start restores the checkpoint on any Mac the organization reserves in the region.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create_macos("base-macos", persistent=True)

# Write state to the disk
sandbox.process.run("echo done > ~/setup.log")

# Stop writes a checkpoint of the disk and frees the sandbox's slot
sandbox.stop()
sandbox.wait_for_state(["stopped"], timeout=60)

# Start restores the checkpoint, possibly on another reserved Mac
sandbox.start()
response = sandbox.process.run("cat ~/setup.log")
print(response.stdout)  # done
ParameterTypeDescription
persistentboolMakes every stop write a checkpoint, which start restores on any Mac the organization reserves. This enables stop and start, and pause and resume. The default is False.

Memory persistence

Memory persistence keeps the runtime state of a sandbox: running processes, open connections, and everything loaded in memory. A sandbox with preserved memory continues from the point it was frozen, instead of starting from a clean boot.

A persistent macOS sandbox keeps its memory across pause and resume. Pause saves the sandbox's memory on its Mac and frees its processor and memory. The paused sandbox keeps its slot. Resume continues every process on the same Mac.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create_macos("base-macos", persistent=True)

# Start a process that holds state in memory
sandbox.process.start("python3 -m http.server 8000")

# Pause saves memory on the Mac; the sandbox keeps its slot
sandbox.pause()

# Resume continues every process on the same Mac
sandbox.resume()
response = sandbox.process.run("curl -s -o /dev/null -w '%{http_code}' http://localhost:8000")
print(response.stdout)  # 200: the server is still running