dd's JIT runs your container's code natively and services its Linux syscalls in userspace — no hypervisor, no Linux kernel. It speaks the Docker Engine API, so docker just works.
What you get
The container's compute runs as native Apple-Silicon instructions; only its syscalls are interpreted — by code that is the kernel.
Implements the Docker Engine API. Your docker run / ps / images / build commands — and existing images — work unchanged.
Native arm64 Linux, x86-64 Linux via the JIT, and macOS — one engine, three runtimes, no VM in any of them.
Native-speed arm64, and x86-64 that laps a VM's qemu emulation — up to 23× on compute, 6× on real openssl, with x86 crypto mapped to ARM hardware. See the numbers →
Overlay image layers (copy-up / whiteout), a TOCTOU-free path-jail VFS, PID/UTS/USER namespaces, a private loopback netns, port publishing, cgroup memory + pids limits.
Namespaces, cgroups, image layers and networking are ordinary userspace state — the gVisor / PRoot lineage, with none of a VM's cost.
A native macOS app plus a ddcli tool install a per-user background daemon and a docker context — never sudo.
Why a JIT, not a VM
Every other way to run Linux containers on a Mac — Docker Desktop, Colima, OrbStack — boots a Linux VM under a hypervisor. That VM is a tax you pay all day. dd deletes it.
| dd — userspace kernel (JIT) | VM-based Docker | |
|---|---|---|
| Resident RAM when idle | None — per-container, freed on exit | Gigabytes for the VM, always on |
| Startup | Process spawn | Boot a Linux VM + the in-VM daemon |
| Bind-mount / file I/O | Direct host filesystem via a path jail | virtiofs/FUSE bridge across the VM |
| Port publishing | Straight to host sockets | Through the VM's NAT layer |
| Battery / background | Nothing running when idle | A VM idling and draining battery |
| Observability | A normal macOS process | An opaque VM |
Honest trade-off: by default the guest runs in one process — fast, and great for code you trust. For untrusted code there's now an opt-in sentry split (the gVisor shape: a deny-default Seatbelt-sandboxed worker with no host authority + a trusted sentry serving syscalls over a shared-memory ring). It's early — core file syscalls today, sockets/exec/fork in progress — so a VM still exposes a narrower attack surface for fully hostile code.
Performance
The same Linux binary, run two ways on one Apple-Silicon Mac: through emulation (how a VM runs cross-arch images) vs. through dd's JIT with no VM. Median of 5, qemu and dd runs alternated — and the dd lane even pays a bridge tax the real app doesn't, so it's conservative.
dd runs arm64 compute at native speed — and beats it on some workloads.
float n-body — the same x86 binary vs qemu software emulation. 10 of 11 workloads win.
openssl cryptox86 AES-NI/SHA-NI mapped to ARM hardware crypto — where qemu runs it in software.
| Workload | VM | dd | dd vs VM |
|---|---|---|---|
| int sieve | 0.74s | 0.48s | 1.55× faster |
| mandelbrot | 0.76s | 0.74s | 1.03× faster |
| matrix multiply | 0.63s | 0.64s | ~parity |
| memcpy | 0.53s | 0.54s | ~parity |
| base64 | 0.65s | 0.65s | ~parity |
| float n-body | 0.16s | 0.17s | ~parity |
| SHA-256 | 0.77s | 0.80s | ~parity |
| qsort | 0.79s | 1.05s | 1.33× slower |
| text scan | 0.49s | 0.66s | 1.35× slower |
| SQLite 600k | 0.35s | 0.52s | 1.48× slower |
| Workload | qemu | dd | dd vs qemu |
|---|---|---|---|
| float n-body | 5.43 s | 0.24 s | 23× faster |
| python — int loop | 10.5 s | 1.14 s | 9.3× faster |
| mandelbrot | 8.02 s | 0.87 s | 9.2× faster |
| matrix multiply | 8.27 s | 1.15 s | 7.2× faster |
| sqlite — insert 200k | 1.47 s | 0.22 s | 6.8× faster |
| openssl AES-128-GCM* | 640 MB/s | 3889 MB/s | 6.1× faster |
| sqlite — 20k selects | 424 ms | 83 ms | 5.1× faster |
| memcpy | 2.42 s | 0.51 s | 4.7× faster |
| qsort | 3.93 s | 1.52 s | 2.6× faster |
| openssl SHA-256* | 372 MB/s | 578 MB/s | 1.6× faster |
| base64 | 4.33 s | 5.09 s | 1.15× slower |
* real openssl speed -evp, 16 KB blocks. An isolated crypto microbench (pure AES-NI/GHASH) hits 13.7× — dd maps x86 crypto to ARM hardware; qemu emulates it.
Across 11 x86 workloads dd beats qemu emulation on 10 of 11 — real openssl, python and sqlite included — and matches a native arm64 VM on compute. The one loss (base64) and the arm64 syscall-heavy rows are exactly the optimization frontier.
The tougher bar — honest
qemu is the fair emulation baseline, but it isn't the fastest x86-on-Mac. OrbStack runs a Linux VM on Apple's latest Virtualization.framework and translates x86 with Rosetta 2 — a much harder bar. We hold dd to it too, and it's already competitive with no VM at all:
| Workload | dd vs OrbStack — arm64 | dd vs OrbStack — x86 (Rosetta) |
|---|---|---|
| container startup | 1.07× faster | 2.06× faster — beats Rosetta |
| redis SET / GET | 1.43–1.46× faster | 0.41–0.54× |
| openssl AES-128-GCM | ≈ parity (0.97×) | ≈ parity (0.98×) |
| openssl SHA-256 | ≈ parity (0.99×) | 1.15× — beats Rosetta |
| tar / gzip | ≈ parity (1.09×) | 1.43× slower |
| sqlite / python | 1.6–1.9× slower | 1.8–2.1× slower |
Against a full Rosetta VM, dd is at or above parity on 5 rows — it starts x86 containers 2× faster than Rosetta (no VM to boot), beats OrbStack on redis by ~1.44× (arm64), and matches or beats Rosetta on crypto — with nothing resident when idle. Where it still trails is interpreter- and fork-heavy x86 work; that gap is closing fast (sqlite-select went 4.5× → 1.6×, python 3.0× → 1.9× in recent passes).
Read the full benchmark writeup — the method, the crypto story, and the honest losses →
Same static/rootfs binary timed two ways, median of 5, qemu & dd alternated on a matrix-busy host; self-timed lanes (openssl/python/sqlite) exclude startup, compute is wall-clock (conservative for dd). Raw numbers: docs/benchmarks-qemu.csv; reproduce compute with make bench.
Get started
Download the app, drop it in Applications, run one setup command. dd registers a background daemon and a docker context — no VM, no sudo. The ddcli tool ships inside the app.
ddcliOne word drops you into a shell in any image — current dir mounted, arch auto-detected.
# a shell in ubuntu, here in this folder $ ddcli ubuntu # a one-off command; force amd64 (x86 JIT) $ ddcli run alpine echo hi $ ddcli run ubuntu --platform linux/amd64 # a macOS container $ ddcli mac
dd registers a dd context, so every ordinary flag flows through.
$ ddcli install # daemon + context (no sudo) $ docker --context dd run -p 8080:80 alpine \ sh -c 'echo hi from $(hostname)' $ docker --context dd pull ubuntu $ docker --context dd ps
Apple-Silicon Macs (arm64), macOS 12+. A single self-contained app — the UI, the ddcli tool and the runtime. Free and open source (MIT).
Alpha software — expect rough edges. Not notarized yet, so on first launch right-click dd → Open once; it runs normally after. ddcli doctor can clear it for you.