carrick
Run unmodified Linux binaries on macOS. No VM, no guest kernel, no daemon.
Carrick loads a Linux ELF binary, runs its ARM64 instructions directly on the CPU
via Apple's Hypervisor.framework, and traps each svc #0 (Linux syscall)
at the hardware boundary. A Rust runtime translates the trapped call to a Darwin
equivalent and resumes the guest. The Linux process becomes a native macOS process —
visible to ps, lsof, kill, and dtrace
on your host.
Install
$ curl -fsSL https://carrick.sh | sh Requires macOS 14+ on Apple Silicon.
Usage
# pull an OCI image and run a command
$ carrick run ubuntu:24.04 /bin/bash -c 'apt-get update && apt-get install -y hello && hello'
Hello, world!
# interactive shell with a real PTY
$ carrick run -t alpine:latest /bin/sh
# run a prebuilt Linux ELF directly — no image pull
$ carrick run-elf ./my-linux-binary --flag value
# bind-mount a host directory into the guest
$ carrick run -v /Users/me/data:/mnt:ro ubuntu:24.04 ls /mnt
# live syscall trace via DTrace USDT probes
$ sudo carrick trace run alpine:latest /bin/echo hi What happens under the hood
When you run carrick run ubuntu:24.04 python3 -m http.server,
here's what actually happens:
$ carrick run ubuntu:24.04 python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 ...
# In another terminal — this works because guest sockets
# bind directly to host interfaces. No port forwarding.
$ curl -s http://localhost:8000/ | head -5
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Directory listing for /</title>
# The guest Python process is a real macOS process:
$ ps aux | grep python
me 41892 0.4 0.1 ... python3 -m http.server
No VM booted. No container runtime started. The Python binary's ARM64 instructions
executed on your CPU at EL0 (unprivileged). When Python called bind(),
carrick trapped the svc #0, decoded it as Linux sys_bind,
and called macOS bind() on a real host socket. The server is listening on
your actual network interface.
How it works
Carrick uses Apple's Hypervisor.framework to create a lightweight execution context
— not a virtual machine. There is no guest kernel, no virtual disk, no BIOS. One
host pthread and one HVF vCPU per guest thread.
Linux Binary (ARM64 EL0) Carrick Runtime (Rust) macOS Kernel
┌──────────────────────┐ ┌──────────────────────────┐ ┌──────────────┐
│ │ trap │ │ │ │
│ guest executes │───────>│ VBAR_EL1 vector catches │ │ │
│ svc #0 (syscall) │ │ hvc #0 exits to host │ │ │
│ │ │ │ │ │
│ │ │ decode x8 (syscall nr) │ │ │
│ │ │ decode x0-x5 (args) │───>│ Darwin API │
│ │<───────│ write result to x0 │<───│ (native) │
│ resumes execution │ │ resume vCPU │ │ │
└──────────────────────┘ └──────────────────────────┘ └──────────────┘ - Trap boundary. Guest executes
svc #0→ synchronous exception toVBAR_EL1→hvc #0exits to the runtime → Rust handler translates and dispatches → result written tox0→ vCPU resumed. - Clean-room. Syscall handlers are written from specs and observed behavior, not from Linux kernel source.
- Memory. Stage-1 identity-mapped page tables with MMU enabled
(
SCTLR_EL1.M=1), so ARM exclusive loads/stores (ldaxr/stlxr) work and mutexes behave correctly. - Concurrency. Per-subsystem locks (fs, creds, proc, signal, mem) — no big kernel lock. Unrelated syscalls run in parallel across threads.
- Fork.
fork(2)→ real macOSfork()+ COW guest pages + rebuilt HVF context.execve(2)→ tear down, reload ELF, resume. - Translation examples.
epoll→kqueue, Linux sockets → Darwin sockets,AF_NETLINKsynthesized, synthetic/procand/sys.
What works today
Carrick runs real workloads end-to-end today — apt-get, Python servers,
the Go runtime test suite. It is not a complete Linux kernel replacement; roughly 75%
of syscalls are implemented. We publish conformance baselines so you can assess fit.
| Workload | Status | Detail |
|---|---|---|
apt-get install | verified | Runs end-to-end including dpkg post-install scripts. |
Python http.server | verified | ThreadingHTTPServer serves concurrent requests from the host. Single-digit ms response times. |
| Go runtime suite | verified | 341/341 tests pass. context 38/38, sync/atomic 95/95. |
| LTP syscall conformance | verified | 568/896 valid tests match (63%). Strong: sched 76%, timers 74%, signals 73%, fs 68%. Weaker: mm 34%, ipc 38%. |
| CPython module parity | verified | 356/482 modules match (74%). Skewed by simple modules; test_subprocess/test_multiprocessing blocked (see limitations). |
Interactive shell (-t) | verified | Real PTY via pty_relay.rs. Ctrl-C, Ctrl-Z, job control work. |
| Docker-style CLI | verified | OCI pull, layer composition, -e/-v/-w/--entrypoint flags. |
Full baseline data: compatibility page.
Performance
Measured via DTrace USDT probes on carrick run … /bin/true:
| Phase | Cost | Note |
|---|---|---|
| First boot | ~90 ms | OCI load + hv_vm_create + page tables + ELF load |
| Fork | ~5.7 ms | HVF context rebuild — ~16× cheaper than boot, no global lock |
| Fork + exec | ~7.8 ms | vs ~1 ms native Linux fork+exec |
| Child teardown | ~7 µs | Effectively free |
| Process teardown | ~175 ms | Kernel reclaiming the large VM mapping |
Known limitations
Carrick is syscall emulation, and that cuts both ways. Not every binary will work. We'd rather be upfront about this than have you find out the hard way.
- ~75% syscall coverage. The LTP baseline shows where the gaps are. Memory management (34%) and IPC (38%) are the weakest subsystems.
- Nested multithreaded fork wedge. Rapid concurrent fork() calls from
multiple threads can wedge the HVF context. This blocks
test_subprocessandtest_multiprocessingin CPython. Root cause unverified. - Rare Go runtime coherence races. Under heavy concurrent memory operations, the Go runtime occasionally hits HVF coherence exceptions.
- No
ptracesupport beyond Phase-1.gdbanddelvedo not work on guest binaries yet. - PTY edge cases.
ttyname//dev/ttydon't fully resolve. Intermittent newline race withlson wide terminals. - ARM64 only. x86_64 Linux binary support via Rosetta 2 is under active research but not functional. Do not expect it to work.
Compared to VMs
Carrick is not a Docker replacement or container orchestrator. It complements containers by running individual Linux binaries without the VM.
| Docker Desktop | Lima / Colima | Full VM | Carrick | |
|---|---|---|---|---|
| Model | Linux VM → containers | Linux VM | Full guest OS | Binary as macOS process |
| Startup | 30–60 s | 10–30 s | Minutes | ~90 ms / ~5.7 ms fork |
| Filesystem | FUSE sync | Mounted share | Virtual disk | Direct host paths |
| Networking | Port forwarding | Shared IP | NAT/bridge | Direct host sockets |
| Host integration | Opaque | Inside VM | Inside VM | ps/lsof/kill/dtrace |
Links
- Documentation — install, CLI reference, tracing
- Compatibility — LTP, Go, CPython conformance baselines
- Blog — engineering notes and deep dives
- GitHub