macOS workspaces
A Mac workspace uses the normal Oblien lifecycle, quotas, networking and writable disk. Firecracker boots a small Linux launcher, which runs the installed macOS system with nested KVM. The image selects the native Mac runtime automatically: terminal, files, commands, watchers and workloads run inside macOS by default.
Linux init starts macOS through the image's guest registry. The shared runtime core supervises its boot driver, negotiates the native agent protocol before application workloads, and coordinates clean shutdown. Stopping or deleting user workloads cannot stop this boot service. Its status is available through macos-info and the explicit local runtime; its bounded log is /var/log/macos-boot.log in Linux. See native guest runtimes for the common protocol and target selection.
Select macOS 26.6.2 in the image picker. The default runtime is the native Mac agent. Remote desktop access starts off; enable it at any time in Desktop. It is a shared image capability, independent of Mac boot settings.
Create a Mac workspace
Find the current image reference with GET /workspace/images?search=macos. Pass the returned image field to the ordinary create endpoint:
POST /workspace
Content-Type: application/json{
"name": "mac-builder",
"image": "oblien/macos-tahoe:26.6.2",
"mode": "permanent",
"config": {
"cpus": 8,
"memory_mb": 24576,
"disk_size_mb": 32768,
"wait_for_init": true
}
}The catalog supplies the matching KVM kernel, enable_kvm: true, wait_for_init: true and the default resources. KVM remains opt-in for other images. Resource and billing authorization still use the outer workspace allocation.
The dashboard returns the workspace immediately and shows Creating while macOS boots. It changes to Ready after native-agent negotiation and a successful command in the default runtime. You can leave the page during startup. A fresh Mac boot usually takes about a minute; the default Mac boot deadline is 300 seconds.
Use top-level wait_ready: false for the same immediate REST response, then poll GET /workspace/:id. REST and SDK callers can still await readiness; see workspace creation. config.ready_check remains an optional application probe with its own command and runtime target; its timeout_seconds allows up to 1,800 seconds for optional software initialization. The Mac operating-system boot deadline remains capped at 600 seconds. The low-level daemon retains wait_for_init, while the workspace service handles readiness in its background creation job.
A new workspace creates a small private qcow2 overlay. It does not download or install macOS again. The native agent binary is already included in the image; on first boot the launcher installs it through the private Mac connection and starts its launch daemon.
Before reporting ready, the launcher applies the prepared account defaults for the clone's new hardware identity and disables the inherited Setup Assistant launch flag. A headless workspace needs no graphical login, setup wizard or Apple Account to run commands and workloads. Sleep is disabled so unattended jobs remain available.
Image preparation closes the builder's applications and clears their saved windows and Terminal sessions, including Tahoe's daemon-container restore data. The first desktop connection opens a clean workspace. This cleanup runs when publishing the image; subsequent workspace boots preserve your own settings and session data.
Shared defaults are prepared for the image's script version and OS build. Fresh clones initialize only hardware-specific preferences when those defaults are current. The launcher retries native-agent health without repeating completed storage checks or provisioning. Boot phase timings are available through the Linux runtime at /run/macos/boot-timings.json; a fresh workspace still performs a real macOS cold boot.
Boot configuration
Xcode 26.6
Choose Xcode 26.6 from the image catalog for Xcode 26.6, Swift 6.3.3, Metal tools and the iOS 26.5 Simulator runtime. It uses the same macOS base and attaches prepared read-only software automatically. An Apple sign-in or download is not required on each workspace. Your signing accounts and certificates remain your own.
const workspace = await client.workspaces.create({
preset: 'macos-xcode',
name: 'ios-development',
ready_timeout_seconds: 1800,
// Optional: config: { desktop: { enabled: true } },
});The default is 8 CPUs, 24 GiB RAM and 16 GiB of private writable storage. Shared tools occupy a 32 GiB software volume without consuming your private disk pool. Resource changes still use the ordinary workspace options and plan limits. Desktop stays off until enabled; terminal, tasks and build commands run natively on macOS in either mode.
Creation registers the prepared tools and waits for the software readiness check.
First activation can take several minutes. Starting a Simulator for the first time
can take longer; readiness confirms the installed tools and registered runtime,
and does not mean a Simulator device has already booted. Use wait_ready: false
to receive the ID immediately and follow the normal creation status.
The installed tools live at /Volumes/OblienXcode26_6, with Xcode available at
/Applications/Xcode.app. Run builds in your private project directories. Do not
update the shared app in place; use a private copy when changing the toolchain.
Runtime settings
Set CPU and RAM once, using the ordinary workspace config.cpus and config.memory_mb. macOS receives every vCPU assigned to Firecracker. Mac RAM is calculated from Linux-visible memory with an internal 2 GiB reserve for the bridge and QEMU, rounded down to 128 MiB. Linux kernel overhead also comes from the workspace allocation. Both systems share that one resource budget.
config.macos controls boot behavior only. It has no separate CPU, RAM or reserve setting. Do not also set the corresponding MACOS_* environment variables through the workspace API. Unknown settings, unsafe cache modes and reserved port mappings are rejected before creation.
| Setting | Default | Behavior |
|---|---|---|
agent | "macos" | "macos" targets the native Mac runtime; "linux" targets the Linux launcher. |
network | "tap" | TAP/vhost network, or "user" for the QEMU user-network compatibility path. |
storage | "nvme" | Virtual NVMe controller, or "sata" for compatibility. |
disk_cache | "none" | Direct I/O avoids an extra Linux page-cache copy. "writeback" remains selectable. |
ports | [] | Up to 32 explicit mappings: { "host": 8080, "guest": 3000, "protocol": "tcp" }. Protocol may be tcp or udp. |
boot_args | Image defaults | Optional Darwin kernel boot-argument string, such as "-v keepsyms=1 debug=0x100". One line, up to 2048 bytes. |
The image requires at least 2 workspace vCPUs, 11 GiB RAM and an 8 GiB private writable disk. The recommended default is 8 CPUs, 24 GiB RAM and 32 GiB writable storage. The shared Mac installation does not count against that private allowance. CPU counts are no longer rounded down to 2/4/8/16: a 12-vCPU workspace exposes 12 vCPUs to the Mac. Host CPU quotas and contention still limit actual throughput.
For a desktop using the workspace allocation:
{
"cpus": 8,
"memory_mb": 24576,
"disk_size_mb": 32768,
"desktop": { "enabled": true },
"macos": {
"agent": "macos",
"network": "tap",
"storage": "nvme",
"disk_cache": "none"
}
}Mac boot settings take effect at a cold boot. Desktop access changes live. CPU/RAM are not hot-plugged into macOS. Resize through the normal workspace resource API and restart; the Mac allocation follows automatically. Old macos.cpus, macos.memory_mb and macos.reserve_memory_mb inputs are rejected by the API. Older resource overrides left in a saved Linux environment are ignored by the new launcher.
boot_args applies to macOS through the instance's private OpenCore configuration. It does not change Firecracker's Linux kernel arguments. The launcher reapplies it on each cold boot; omitting it restores the image's defaults, and an explicit empty string clears the override value. This configuration accepts typed boot settings, not arbitrary QEMU command-line fragments.
config.macos is a create-time workspace option. The low-level daemon stores its translated environment contract. Operators can change that contract on a stopped VM and relaunch it; creating a new workspace is the supported way to select a different image or replace the agent implementation. Normal cmd and inline workloads can be supplied at creation and start after runtime readiness. The image owns boot; replacing its low-level command or entrypoint is unsupported.
Native terminal, files and commands
The existing runtime APIs operate inside macOS by default. No separate desktop session is required for terminal or file access.
Creation has no Terminal and files runtime choice. After creation, the Terminal and Files tabs show a Runtime selector when the workspace reports more than one runtime. It starts at macOS (default); select Linux (local) to inspect the launcher. This view selection carries between the two tabs during your visit, without changing the default for API commands, workloads or SSH. Each runtime has separate terminal sessions and file contents. Save or close changed files before switching. Ordinary single-runtime workspaces do not show the selector.
const ws = client.workspace(workspace.id);
const rt = await ws.runtime();
const result = await rt.exec.run(['/usr/bin/sw_vers']);
console.log(result.stdout);
await rt.files.write({
fullPath: '/var/root/hello.txt',
content: 'Hello from the Mac runtime\n',
});The agent runs as root under launchd, with /var/root as its home and /bin/zsh as its default shell. The desktop login account is oblien. Use Mac paths and commands: sw_vers, launchctl, sysctl, /Users/oblien and /var/root. Linux package managers and Linux-only utilities do not become Mac commands.
The prepared image grants Full Disk Access to the native agent and the Mac SSH service. This lets the runtime file API, terminals, tasks and SSH access Desktop, Documents and Downloads in headless workspaces without a first desktop login. macOS checks this permission separately from Unix root access.
SSH uses the oblien account with either a password or your public key. Run sudo -i for a root shell, or sudo -n command for unattended root commands; no sudo password is required.
Native Mac workspaces support SSH and SCP whether desktop access is on or off. Open the workspace's SSH tab and enable SSH. The displayed connection command logs directly into macOS as oblien, with /Users/oblien as its home. Enable returns the current account password; use Change Password to set your own. You can also save an SSH public key (for example, ~/.ssh/id_ed25519.pub) and use either login method.
The API uses the existing POST /workspace/:id/ssh/key, POST /workspace/:id/ssh/password and POST /workspace/:id/ssh/enable endpoints. GET /workspace/:id/ssh returns auth_methods: ["password", "publickey"], password_auth_enabled and the connection commands. The SDK exposes workspaces.ssh.setKey(id, { public_key }), workspaces.ssh.setPassword(id, { password }) and workspaces.ssh.enable(id). Enablement and password changes persist across workspace restarts. For an existing key-only Mac, setting a password activates password login without restarting the VM.
The SSH password is also the oblien desktop login password. Changing it through this API updates the Mac account and its private workspace credential together, so the desktop viewer's Type login password remains current. It is not stored in the workspace database or returned by the status endpoint. Key access, the native root runtime and passwordless sudo remain available. Direct root SSH login stays disabled.
The Linux bridge forwards the SSH connection to the Mac's built-in SSH server. Replacing your managed public key preserves the private launcher key and any manually installed keys. Disabling public SSH removes only this forwarding workload and its bastion route, preserving boot, native runtime access and the private launcher connection. Selecting agent: "linux" instead keeps the regular Linux SSH flow.
Terminal sessions, command exit codes, file operations, search, filesystem watchers, process workloads, logs and runtime proxy requests use the same interfaces as Linux workspaces. Content search installs Darwin ripgrep once through the normal rt.search.init() flow when needed. Native watchers use kqueue, with a bounded file-descriptor budget. A watcher can cover up to 8192 entries; exclude large dependency/build directories. macOS privacy permissions and application requirements still apply to GUI automation.
Both agents use the same API schema. Commands retain the target OS's syntax: /usr/bin/sw_vers runs on macOS and /usr/bin/uname -s runs on either OS. The bridge does not translate package-manager commands or filesystem paths between systems. GET /runtime returns the selected OS, architecture, shell, home directory and CPU count.
To use the Linux terminal and files while keeping macOS as the default, select the local runtime:
import { Runtime } from 'oblien/runtime';
const mac = new Runtime({ token: gatewayToken });
const linux = mac.forTarget('local');
console.log(await mac.targets()); // negotiated macos and local runtimes
console.log(await mac.info()); // darwin, /var/root, /bin/zsh
console.log(await linux.info()); // linux, /root, boot_subsystem status
await linux.exec.run(['/usr/bin/uname', '-s']);
await linux.files.list({ dirPath: '/var/log' });The HTTP equivalent is /runtimes/local/exec, /runtimes/local/terminals and /runtimes/local/files. /runtimes/macos selects Darwin explicitly. The image retains /linux as a compatibility alias. These routes require the same workspace token; rotation and disable revoke sessions on both systems. Workloads follow the default unless their request supplies an explicit target, which persists across cold starts. Even with agent: "linux", the Mac agent remains available as target: "macos".
Use these explicit control commands through the task API when you need the outer launcher:
{ "target": "local", "cmd": ["/bin/uname", "-s"] }{ "cmd": ["/usr/local/bin/macos-info"] }macos-info reports effective boot settings and readiness without credentials. macos-ready performs a live check. macos-exec uses the private pinned SSH connection to execute as the Mac desktop user, including when agent: "linux" is selected. The explicit Linux helpers use their full /usr/local/bin/... paths so an ordinary Mac command cannot be mistaken for a launcher command.
Browser desktop and remote control
Choose Desktop → Connect. It enables access if needed and replaces the connection choices with the desktop. Back closes the viewer; Open in browser moves it to a separate window. The browser prepares the Mac account's graphical session automatically, without clicking or typing a password. No reboot is needed. Optionally set config.desktop: { enabled: true } at creation. macos.mode is no longer a boot option.
Opening Desktop starts no stream until you connect. Browser connections close after five minutes without input or one minute in the background; Reconnect resumes them. The Mac and its jobs keep running. Inline, separate-window and VNC access share one desktop access switch.
The browser supports native Mac text clipboard transfer: Paste or Ctrl+V / ⌘V sends text from your computer, and Copy from desktop retrieves copied Mac text. Unicode and multiline text are supported after graphical login. Use the Clipboard text box if browser permissions require a manual gesture. See remote desktop for the complete flow and limits.
The first graphical login opens the prepared desktop without Apple's first-run setup questions. The local-account login remains enabled, with a different password for each workspace. Agent readiness includes the automatic setup defaults and does not require a graphical login.
Programmatic clients can open the same viewer with a current gateway token:
https://workspace.oblien.com/desktop#token=GATEWAY_TOKENThe viewer consumes and removes the fragment, then authenticates HTTP and WebSocket requests in headers. Its static page contains no workspace data; /desktop/status, credentials and the connection remain protected by the existing runtime token checks. Rotating the token or disabling Runtime API access closes active desktop connections. Treat the initial desktop link as a workspace credential. The Mac login password is fetched only when the user chooses to type it.
QEMU keeps its VNC provider on a private Unix socket inside Linux. The shared runtime controls access to it; turning access off disconnects browser and VNC clients while the Mac, native agent, SSH and workloads keep running. The setting is stored in the private writable layer and survives restart.
Native VNC apps with SSH tunneling connect directly through Oblien using Desktop → Get VNC connection, including mobile clients. The eight-hour connection is scoped to this desktop and needs no enabled Mac SSH service. The optional oblien desktop vnc WORKSPACE_ID CLI tunnel remains available on 127.0.0.1:5901 on your computer. No public VNC port is required. See remote desktop for the CLI download, browser controls and shared API/SDK methods.
The OS, resource allocation and graphical services are the same whether a client is connected or not; there is no separate headless Mac image.
This is a software-rendered virtual display. Metal, GPU acceleration, accelerated video and native Apple Silicon performance are not provided.
Networking and app ports
Outbound Mac traffic uses the Linux launcher and then the normal workspace network policy. TAP mode avoids QEMU's user-mode NAT datapath and uses vhost when available. The inner private address is 10.0.2.15, with gateway 10.0.2.2; each outer VM has its own isolated instance of that subnet.
For runtime HTTP proxy requests with the native agent selected, the target service is reached inside the Mac. For a normal published workspace port or domain, first map the outer port to the Mac application:
{
"macos": {
"ports": [{ "host": 8080, "guest": 3000, "protocol": "tcp" }]
}
}Run the Mac service on 0.0.0.0:3000, then publish outer port 8080 using the existing ports/domain flow. A mapping alone does not make a port public or bypass workspace firewall rules. Keep the control ports reserved: 22, 5900, 9990, 18080, 19990 and 19999 cannot be mapped.
Private Mac agent traffic uses a separate, randomly generated transport credential per instance. Workspace token rotation does not require restarting macOS or changing its private transport credential.
Storage, stop and restart
The storage chain is:
shared immutable Linux rootfs
└─ installed read-only macOS qcow2 base
per-workspace writable ext4
└─ private macOS qcow2 overlay, firmware, identity and keysThe launcher checks the overlay's backing-file identity and mounts the base read-only. New workspaces get separate Mac machine identities, SSH keys, passwords, firmware variables and native-agent credentials. The base Mac disk is not copied for each launch.
Firecracker also exposes the shared rootfs as a read-only block device, and QEMU opens the Mac backing image read-only. Files, installed applications and settings changed inside a workspace go to its private writable layer. Guest root access does not grant write access to the shared host image. Those changes do not appear in other workspaces; publishing them for future workspaces requires explicitly capturing a new image version.
Normal stop and restart request a Mac shutdown and wait for QEMU to exit before stopping Firecracker. A failed clean shutdown is reported instead of silently claiming success. Force deletion and host failure can still interrupt the guest. Writable data persists through normal cold restarts. Pause stops QEMU before freezing Firecracker, then Resume thaws Firecracker before QEMU. This preserves the current Mac session and its live memory. Pause keeps host RAM allocated; it does not write a memory snapshot or change CPU/RAM allocation.
Stopping a workspace revokes its public runtime credential, as for other workspace images. After relaunch, reconnect through the workspace API to obtain the current token. The Mac account password, hardware identity and private bridge credentials persist.
The current Mac image automatically reclaims idle backing through its native balloon driver, QEMU and the existing Linux free-page reporter, then returns every balloon page to macOS. It starts in the background after Mac readiness and uses the ordinary workspace RAM allocation. It pauses growth during CPU work, preserves free headroom, and releases pages on memory pressure or loss of its helper. Each pass has a deadline and a cooldown. An interrupted pass can retry when the Mac becomes quiet; a completed pass requires new idle backing before starting again. New workspaces require no memory setup. Existing workspaces retain their exact image unless explicitly updated.
During a reclaim pass, raw Mac statistics count the collected pages as wired memory, so usage can temporarily rise. The controller releases them afterward and keeps no standing balloon at idle. The metrics are not relabeled to hide this allocation. Host savings remain until those pages are used again. This is still conventional ballooning, with different overhead from Linux free-page reporting. Host usage also includes the Linux bridge, QEMU and cache, so the host and Mac percentages need not match.
The dashboard shows Hibernate separately from Pause. Pause remains available with KVM enabled and keeps RAM allocated. The daemon reports lifecycle.pause_supported and lifecycle.snapshot_supported; the API and dashboard use those capabilities instead of checking the image name or KVM option. The current Firecracker backend does not support nested KVM snapshot restore, so Hibernate remains unavailable. Memory-releasing hibernation would require verified nested-state restore in the VMM or a separate inner-hypervisor checkpoint implementation. A Mac reboot after Pause is a failure rather than expected behavior. Keep the separate outer auto_reclaim.enabled policy disabled in Mac configuration; it is not the switch for the image's native memory subsystem. Dirty-page tracking defaults to disabled for KVM VMs. Both stop/relaunch and private writable-disk migration remain available.
The shared disk image does not automatically share RAM between workspaces. The source-owned scripts/macos-image/MEMORY.md and memory/README.md describe the control path, measurements, rebuilds for another OS version and the operator recovery option.
config.disk_size_mb sets the private writable layer, just as for Linux workspaces. The default is 32 GiB (32768 MiB); the minimum is 8 GiB (8192 MiB). The installed OS lives in the shared base, so each fresh workspace does not need another 25–30 GiB copy of macOS. New files and changes to shared files consume private space. Filesystem metadata, reserved blocks and a 1 GiB allowance for bridge operation and clean shutdown reduce the usable budget.
The base has a 64 GiB virtual disk. This is address space for the shared OS and private changes, not 64 GiB of private storage allocated to each workspace. Larger writable allocations can grow the virtual disk and APFS automatically. Reducing the writable allocation checks whether the actual private data fits and preserves the existing APFS size; it does not shrink or truncate the installed OS.
Use the normal workspace resource update to grow or shrink disk_size_mb. A running Mac is shut down cleanly and restarted after the new writable layer is committed. A stopped workspace uses a Linux-only maintenance boot and remains stopped, without starting user workloads. The API reports failure if private data cannot fit in a requested smaller layer.
Native runtime disk statistics report the writable budget and usage, with separate filesystem_total_bytes and filesystem_free_bytes for APFS. Mac tools such as df and Disk Utility still show virtual filesystem capacity. That larger figure does not increase the workspace's writable allowance. If the private budget fills, the image requests a clean Mac shutdown before exhausting the backing filesystem; the Linux control channel remains available. Increase workspace storage to boot the Mac again.
This behavior requires the current image and both current agents. Workspaces created from older images keep their previous storage driver and limits; updating the catalog or host daemon alone does not convert an existing clone.
Scale allows 250 GiB per workspace and a 1 TiB total disk pool, so the Mac defaults fit. The Mac RAM requirement still exceeds Hobby and Pro running-memory pools. See Resource pools for the complete plan limits.
Build, upgrade and migrate
The dedicated tools and operator instructions are in /home/vms/scripts/macos-image/, including OPERATIONS.md for the complete prepare → customize → capture → verify → promote → migrate workflow. Shared Go agent/protocol code stays in the normal /home/vms packages. The API entry point is /home/api/images/macos/build.sh. The host copy is /opt/firecracker/macos-images/current/tooling/. /home/vms-backups/ holds private image artifacts and operation records; workspace boot does not depend on scripts hidden there.
UPGRADES.md in that source directory covers reapplying Oblien preparation to another macOS version. INSTALL.md covers starting from an empty disk, with selectable recovery media and a pinned OSX-KVM revision. The checklist includes setup defaults, native agent and SSH privileges, privacy approvals, headless/desktop readiness, guest routing, resource resizing, clone isolation and a verified backup. An OS change uses a disposable preparation Mac and a temporary candidate. Publish it under the canonical OS-version tag after verification; ordinary rebuilds keep the same name. The installer and any required privacy approvals happen during image preparation, while fresh workspaces only boot and provision their private state.
For optional applications such as Xcode and iOS Simulator, use a prepared software disk. Several workspaces can read the same saved software while keeping their projects, Simulator devices and credentials private. The plain Mac image stays the same.
Changes to the complete Mac environment use a disposable clone. Run build.sh --prepare-only --from-image CURRENT_TAG --image BUILD_TAG, customize that clone, then use build.sh --source-vm VM_ID --from-image CURRENT_TAG --image BUILD_TAG to capture a new immutable base. Preserve the verified agent binaries when only changing applications or settings. New workspaces receive separate writable layers.
Graphical preparation is done once for the image when needed, including macOS privacy approvals. Fresh workspaces do not repeat those UI actions, the installer or Setup Assistant. The console tool also works with headless preparation Macs through a private QMP socket. Do not capture a user's working Mac into a shared template: capture preserves Mac application data, so the preparation VM must contain only settings/software intended for all its future clones.
When changing agent code, compile both transports from the same checkout:
cd /home/vms
make build-macos-agents
make buildDeploy the tested daemon through the normal VMS deployment flow. Then, on the host holding the installed Mac base:
sudo bash /home/vms/scripts/macos-image/build.sh --runtime-only \
--from-image oblien/macos-tahoe:26.6.2 \
--image oblien/macos-tahoe:build \
--work-dir /opt/firecracker/macos-images/workThis builds a small Docker child layer and one sparse immutable rootfs cache. It includes Python launcher modules, both compiled agents, the launchd service, desktop viewer, setup defaults and networking dependencies. It preserves the installed Mac parent layer. Defaults are applied automatically in each clone's writable disk before readiness. The sparse cache builder uses Docker overlay2 and requires at least 50 GiB free; operation records contain artifact hashes and the parent image identity.
Full Disk Access is tied to the approved Mac executable. A runtime-only update must preserve that binary. When changing the Mac agent, install and approve the new binary in a disposable preparation Mac, run verify-access.py, and capture a new base. Verify a fresh headless clone before promoting it; a Linux launcher update alone cannot grant Mac privacy permissions.
To capture changes made inside the Mac itself, use build.sh --image BUILD_TAG without --runtime-only. That flow creates a disposable preparation VM, installs the first-boot helper, applies the same account defaults, compacts APFS and its virtual disk to the 64 GiB base default, shuts down macOS cleanly and flattens its disk into a new backing image. It needs substantially more free space (at least 140 GiB). Original VMs and bases remain available.
TAP mode requires the separate KVM guest kernel built from kernel-config.json. The kernel contains built-in KVM, Intel nested virtualization, TUN, vhost-net and nftables NAT support. build-kernel.py starts from a working KVM configuration and refuses to overwrite an existing kernel directory. The current kernel ID is kvm-macos-6.1.187. The host must expose Intel VMX and have nested KVM enabled; this image is not an ARM or AMD portability guarantee.
Export/import with migrate.py transfers the immutable rootfs, Docker image, kernel, source tooling and checksums. Keep that bundle private because the base contains its bootstrap credential. Import validates hashes and refuses conflicting image/kernel names. A running workspace's writable disk is separate: shut it down cleanly, export it through the normal VM disk workflow, and restore it against the matching base and kernel.
Verify the candidate, including native headless access, desktop, isolation and any changed storage/runtime behavior. Delete its disposable test VMs, then replace the current image:
sudo bash /home/vms/scripts/macos-image/build.sh --publish \
--from-image oblien/macos-tahoe:buildThe image name stays oblien/macos-tahoe:26.6.2 in the catalog, daemon and Docker. The publisher atomically replaces the read-only rootfs and removes the temporary build tag/cache. Checksums and OS build details belong in the manifest and release.json; internal rebuilds do not add dates or revision suffixes. Ordinary rebuilds need no catalog change.
Running workspaces retain their open base. A private Mac qcow2 requires identical installed backing bytes: when an OS/application capture changes those bytes, retire or recapture dependent development workspaces before publishing. The publisher checks this instead of silently rebasing their data. A complete OS variant has its own canonical image name; an optional Xcode software disk has its own managed disk ID.
Keep one verified standalone backup at /home/vms-backups/macos-tahoe/current/. It contains the complete rootfs, Docker archive, kernel, approved agents and source tools. No earlier image or delta chain is needed for recovery. Replace the backup only after verifying its replacement.
Verified behavior
On September 18, 2026, a fresh workspace from the canonical image with 8 CPUs, 16 GiB RAM and 32 GiB writable storage reached native readiness in 47 seconds. Root file APIs and native SSH passed before any graphical login. The browser desktop connected without Setup Assistant and had no restored application windows.
Coordinated Mac Pause/Resume preserved the same boot and 512 MiB of live random data, including a separate 90-second pause check after daemon recovery. Pause retains RAM. The dashboard's separate Hibernate action saves and restores Linux memory and releases its VMM process; nested Mac hibernation is unavailable.
The first desktop connection triggered substantial native login/startup work. Later reconnection was much faster. Mac reclaim remains enabled, but the remaining Mac/QEMU memory gap is not fully resolved. These are measured samples, not readiness or performance guarantees.
The reusable runtime, resource, storage and isolation checks are in the VMS operator guide. The latest detailed results are in /home/vms/docs/verification/macos-current.json.
Troubleshooting
| Symptom | Check |
|---|---|
| Creation stays in readiness | Inspect /var/log/macos-boot.log through the Linux runtime and macos-info; Linux being up is not proof that macOS is ready. |
| TAP is unavailable | Select the matching Mac kernel. network: "user" is the explicit compatibility option. |
Commands print Linux | Check macos.agent; use target: "macos" for native Mac access when Linux is intentionally selected. |
| Desktop is disabled | Enable it in the Desktop tab or call runtime.desktop.enable(); no reboot is needed. |
| Browser console disconnects after token rotation | Reopen it from Connect to obtain the current token. |
| Application port is unreachable | Bind on the Mac network interface, add the Mac port mapping, and publish the outer port under normal workspace policy. |
| Mac memory is below the outer allocation | The launcher reserves memory for Linux and QEMU; automatic RAM also accounts for actual Linux-visible memory. |
| Low graphical performance | The display is software rendered; use it for control and diagnostics rather than GPU workloads. |