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 beats a VM's qemu emulation — up to 21× on floating-point. 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 an Apple M5 Pro: inside a Linux VM (how VM-based Docker works) vs. through dd's JIT with no VM. Median of 7 — 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.
vs. emulating the same x86 binary under qemu in a VM.
structural wins on top — no machine to boot, direct host I/O.
| 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 | VM | dd | dd vs VM |
|---|---|---|---|
| float n-body | 5.18s | 0.20s | 26× faster |
| matrix multiply | 8.08s | 0.65s | 12× faster |
| mandelbrot | 7.62s | 0.81s | 9.4× faster |
| SQLite 600k | 2.88s | 0.73s | 4.0× faster |
| qsort | 3.86s | 1.36s | 2.8× faster |
| memcpy | 2.30s | 0.92s | 2.5× faster |
| int sieve | 1.26s | 0.63s | 2.0× faster |
| text scan | 1.36s | 0.88s | 1.6× faster |
| SHA-256 | 2.60s | 1.83s | 1.4× faster |
| base64 | 4.10s | 4.71s | 1.15× slower |
Across 10 workloads, dd beats qemu-in-a-VM on 9 of 10 for x86, and matches a native arm64 VM on compute. Where it's behind — allocation/syscall-heavy work (arm64 SQLite, qsort) — is exactly the optimization frontier.
Compute micro-benchmarks, median of 7, same static binary timed two ways (make bench). The userspace-kernel tax shows on syscall-heavy work; a Rosetta-backed VM would narrow the x86 gap.
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.