Summary
Evaluate replacing — or supplementing — the macOS Lima backend with Apple's container tool and its container machine abstraction. Both run guest VMs on Apple's Virtualization.framework, so this is a question of which orchestration layer coop delegates to on macOS, not a change to the virtualization technology itself.
Empirical testing on a real macOS 26.5.1 / Apple-Silicon host (notes below) shows an Apple container backend is viable as an opt-in third backend on macOS 26+. This issue lays out why we'd consider the shift, what we'd gain, and what it would cost.
A note on scope. The relevant comparison is Apple container vs Lima, since that's what it would replace or supplement on macOS — not vs Firecracker (the Linux backend). Lima already runs a full Ubuntu kernel with CONFIG_NF_TABLES and CONFIG_IP_NF_RAW, so the two kernel workarounds coop maintains (iptables-legacy, DOCKER_INSECURE_NO_IPTABLES_RAW) are Firecracker-only and already absent from the Lima path (src/lima.rs:1373). Apple container matches Lima here — it does not improve on it. The honest gains over Lima are first-party tooling and architecture fit; the kernel-workaround story is irrelevant to this decision and is called out below only to forestall the apples-to-oranges framing.
Assumed floor: macOS 26+ (Apple Silicon). Older-macOS support would have to be dropped for an Apple-container-only backend; keeping Lima as a fallback avoids that.
Background
Today coop selects a backend at compile time: Lima on macOS (wrapping Virtualization.framework via limactl), Firecracker on Linux. The shared SshTarget/Backend abstraction in src/backend.rs means all SSH-based operations — config injection, workspace sync, VS Code, port forwarding — are backend-agnostic. A new macOS backend only has to satisfy the same contract: create named VMs, give each a host-reachable SSH endpoint, support live host-directory mounts, and tear down cleanly.
Apple container reached 1.0.0 on 2026-06-09. container machine boots a Linux VM (Apple's Containerization kernel) into which we can bake an Ubuntu+systemd guest image — the same golden-image pattern we already use for Lima.
Why consider the shift — what we'd gain
-
First-party, Apple-maintained tool. container is Apple's own project tracking Virtualization.framework directly, versus Lima as a third-party wrapper. Fewer layers between coop and the platform, and a tighter coupling to the OS it targets.
-
Live mounts at full virtiofs parity. container run --volume /any/abs/path:/dst (or --mount source=…,target=…) live-mounts any absolute host directory — bidirectional virtiofs, changes visible immediately on both sides, matching Lima's live-mount semantics. No $HOME restriction (verified in Parser.swift/validateMount). Only constraint: the host path must exist and be absolute (no auto-create).
-
The SshTarget/Backend abstraction holds. Each machine gets a host-reachable private IP (192.168.64.x via vmnet). Baking sshd + a pubkey into the guest image (the golden-image pattern we already use) and reaching it over the private IP keeps the entire shared SSH layer intact — no coop-side rework.
-
Concurrent multi-machine boot works. coop runs several named VMs at once. Created coop-a and coop-b simultaneously; both showed running with distinct IPs. set-default only governs whether -n can be omitted, not exclusivity.
What it would cost — drawbacks and risks
-
macOS 26 floor. An Apple-container-only macOS backend drops pre-26 support. Mitigation: keep Lima as the macOS fallback and add container as an opt-in third backend, paying the cost of maintaining two macOS paths instead of one.
-
1.0.0 maturity / lifecycle flakiness. container is freshly 1.0.0. In testing, stop/delete were clean and instant, and of the lifecycle bugs originally flagged only #861 remains open (the rest are closed; #977 was a fixed old-version regression, not a live --rm data-loss bug). Still, this is young software and the lifecycle surface deserves caution.
-
Machine IPs are not stable across reboots. Observed drift (.6→.7, .8→.9) on restart. coop must re-resolve the IP via container machine inspect on each start rather than caching it. This is a real change to how coop tracks an instance's SSH endpoint.
-
container machine run exec is flaky and quirky — intermittent Operation not supported by device during boot races; it word-splits the command (sh -c "echo AAA" runs echo with AAA as $0); and it does not forward piped host stdin. coop already drives guests over SSH, so this is fine for runtime — but provisioning steps cannot rely on machine run to inject files. All provisioning must be baked into the golden image or pushed over SSH.
-
No disk-size knob — coop resize has no analog. machine create/machine set expose only cpus, memory, home-mount. Disk is grow-on-demand EXT4 (sparse on host APFS; the DISK column grew 75M→700M+ as content unpacked). coop's resize command can't be implemented against container. Likely moot given grow-on-demand, but it's a feature-parity gap to acknowledge.
-
--publish loopback forwarding is buggy on macOS 26.1–26.3 (#919, #1321). Use the direct private IP, not -p. A host-side macOS "Local Network" permission gate can apply (not hit in the CLI context tested).
Empirical confirmation (macOS 26.5.1, Apple Silicon, container 1.0.0)
All five originally-open questions now have verdicts from a real host:
| Question |
Verdict |
| Concurrent multi-machine boot |
✅ Two machines running with distinct IPs |
| dockerd in Ubuntu+systemd, end-to-end |
✅ docker run hello-world pulled+ran; raw table present (full kernel, like Lima — no FC-style workarounds needed) |
| SSH over the private IP |
✅ Ping + TCP/22 + key auth as host-matching user, end-to-end |
| Disk sizing / resize |
✅ No disk knob; grow-on-demand EXT4 (resize moot) |
| Lifecycle robustness on 1.0.x |
✅ Clean stop/delete; only #861 still open |
Two corrections to earlier desk research: the disk is EXT4 grow-on-demand images sparse on host APFS (not "APFS thin-provisioned" per-machine volumes); and #977 is fixed, not a live data-loss bug.
Recommendation
The case here is weaker than the Firecracker comparison would suggest, because vs Lima there is no kernel-workaround win — both run full kernels. The genuine upside over Lima is first-party tooling and tighter OS coupling, with mount/SSH parity (not improvement). Against that sit real costs: a macOS 26 floor, 1.0.0 lifecycle maturity, per-reboot IP churn, provisioning that can't use machine run, and no coop resize analog.
Given that the upside over Lima is modest and the costs are concrete, the recommendation is to treat this as exploratory / opt-in only — worth a prototype third backend behind the existing compile-time selection (Lima stays the macOS default), but not a reason to drop Lima. Revisit if Apple container matures past 1.0.x or if a future need (e.g. dropping the third-party Lima dependency, or a capability Lima lacks) tips the balance.
Implementation notes for whoever picks this up
- New backend module mirroring
src/lima.rs, wired into the Backend enum in src/backend.rs.
- Golden image:
ubuntu:24.04 + systemd + docker.io + openssh-server, pubkey baked into /etc/skel.
- Re-resolve the guest IP via
container machine inspect on every start; never cache it.
- Drive all guest interaction over SSH; never depend on
container machine run for provisioning or stdin.
- No
resize implementation; document the grow-on-demand behavior instead.
- Reuse direct private-IP connectivity; avoid
--publish.
Summary
Evaluate replacing — or supplementing — the macOS Lima backend with Apple's
containertool and itscontainer machineabstraction. Both run guest VMs on Apple's Virtualization.framework, so this is a question of which orchestration layer coop delegates to on macOS, not a change to the virtualization technology itself.Empirical testing on a real macOS 26.5.1 / Apple-Silicon host (notes below) shows an Apple
containerbackend is viable as an opt-in third backend on macOS 26+. This issue lays out why we'd consider the shift, what we'd gain, and what it would cost.A note on scope. The relevant comparison is Apple
containervs Lima, since that's what it would replace or supplement on macOS — not vs Firecracker (the Linux backend). Lima already runs a full Ubuntu kernel withCONFIG_NF_TABLESandCONFIG_IP_NF_RAW, so the two kernel workarounds coop maintains (iptables-legacy,DOCKER_INSECURE_NO_IPTABLES_RAW) are Firecracker-only and already absent from the Lima path (src/lima.rs:1373). Applecontainermatches Lima here — it does not improve on it. The honest gains over Lima are first-party tooling and architecture fit; the kernel-workaround story is irrelevant to this decision and is called out below only to forestall the apples-to-oranges framing.Assumed floor: macOS 26+ (Apple Silicon). Older-macOS support would have to be dropped for an Apple-
container-only backend; keeping Lima as a fallback avoids that.Background
Today coop selects a backend at compile time: Lima on macOS (wrapping Virtualization.framework via
limactl), Firecracker on Linux. The sharedSshTarget/Backendabstraction insrc/backend.rsmeans all SSH-based operations — config injection, workspace sync, VS Code, port forwarding — are backend-agnostic. A new macOS backend only has to satisfy the same contract: create named VMs, give each a host-reachable SSH endpoint, support live host-directory mounts, and tear down cleanly.Apple
containerreached 1.0.0 on 2026-06-09.container machineboots a Linux VM (Apple's Containerization kernel) into which we can bake an Ubuntu+systemd guest image — the same golden-image pattern we already use for Lima.Why consider the shift — what we'd gain
First-party, Apple-maintained tool.
containeris Apple's own project tracking Virtualization.framework directly, versus Lima as a third-party wrapper. Fewer layers between coop and the platform, and a tighter coupling to the OS it targets.Live mounts at full virtiofs parity.
container run --volume /any/abs/path:/dst(or--mount source=…,target=…) live-mounts any absolute host directory — bidirectional virtiofs, changes visible immediately on both sides, matching Lima's live-mount semantics. No$HOMErestriction (verified inParser.swift/validateMount). Only constraint: the host path must exist and be absolute (no auto-create).The
SshTarget/Backendabstraction holds. Each machine gets a host-reachable private IP (192.168.64.xvia vmnet). Bakingsshd+ a pubkey into the guest image (the golden-image pattern we already use) and reaching it over the private IP keeps the entire shared SSH layer intact — no coop-side rework.Concurrent multi-machine boot works. coop runs several named VMs at once. Created
coop-aandcoop-bsimultaneously; both showedrunningwith distinct IPs.set-defaultonly governs whether-ncan be omitted, not exclusivity.What it would cost — drawbacks and risks
macOS 26 floor. An Apple-
container-only macOS backend drops pre-26 support. Mitigation: keep Lima as the macOS fallback and addcontaineras an opt-in third backend, paying the cost of maintaining two macOS paths instead of one.1.0.0 maturity / lifecycle flakiness.
containeris freshly 1.0.0. In testing,stop/deletewere clean and instant, and of the lifecycle bugs originally flagged only #861 remains open (the rest are closed; #977 was a fixed old-version regression, not a live--rmdata-loss bug). Still, this is young software and the lifecycle surface deserves caution.Machine IPs are not stable across reboots. Observed drift (.6→.7, .8→.9) on restart. coop must re-resolve the IP via
container machine inspecton each start rather than caching it. This is a real change to how coop tracks an instance's SSH endpoint.container machine runexec is flaky and quirky — intermittentOperation not supported by deviceduring boot races; it word-splits the command (sh -c "echo AAA"runsechowithAAAas$0); and it does not forward piped host stdin. coop already drives guests over SSH, so this is fine for runtime — but provisioning steps cannot rely onmachine runto inject files. All provisioning must be baked into the golden image or pushed over SSH.No disk-size knob —
coop resizehas no analog.machine create/machine setexpose onlycpus,memory,home-mount. Disk is grow-on-demand EXT4 (sparse on host APFS; the DISK column grew 75M→700M+ as content unpacked). coop'sresizecommand can't be implemented againstcontainer. Likely moot given grow-on-demand, but it's a feature-parity gap to acknowledge.--publishloopback forwarding is buggy on macOS 26.1–26.3 (#919, #1321). Use the direct private IP, not-p. A host-side macOS "Local Network" permission gate can apply (not hit in the CLI context tested).Empirical confirmation (macOS 26.5.1, Apple Silicon,
container1.0.0)All five originally-open questions now have verdicts from a real host:
runningwith distinct IPsdocker run hello-worldpulled+ran; raw table present (full kernel, like Lima — no FC-style workarounds needed)Two corrections to earlier desk research: the disk is EXT4 grow-on-demand images sparse on host APFS (not "APFS thin-provisioned" per-machine volumes); and #977 is fixed, not a live data-loss bug.
Recommendation
The case here is weaker than the Firecracker comparison would suggest, because vs Lima there is no kernel-workaround win — both run full kernels. The genuine upside over Lima is first-party tooling and tighter OS coupling, with mount/SSH parity (not improvement). Against that sit real costs: a macOS 26 floor, 1.0.0 lifecycle maturity, per-reboot IP churn, provisioning that can't use
machine run, and nocoop resizeanalog.Given that the upside over Lima is modest and the costs are concrete, the recommendation is to treat this as exploratory / opt-in only — worth a prototype third backend behind the existing compile-time selection (Lima stays the macOS default), but not a reason to drop Lima. Revisit if Apple
containermatures past 1.0.x or if a future need (e.g. dropping the third-party Lima dependency, or a capability Lima lacks) tips the balance.Implementation notes for whoever picks this up
src/lima.rs, wired into theBackendenum insrc/backend.rs.ubuntu:24.04 + systemd + docker.io + openssh-server, pubkey baked into/etc/skel.container machine inspecton every start; never cache it.container machine runfor provisioning or stdin.resizeimplementation; document the grow-on-demand behavior instead.--publish.