dd
dd
Alpha release

Run Linux containers on macOS — with no VM.

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.

Apple Silicon Docker Engine API 3 runtimes: Linux arm64 · x86-64 · macOS Installs without root

What you get

A userspace kernel, not a virtual machine

The container's compute runs as native Apple-Silicon instructions; only its syscalls are interpreted — by code that is the kernel.

Drop-in Docker

Implements the Docker Engine API. Your docker run / ps / images / build commands — and existing images — work unchanged.

Three guest runtimes

Native arm64 Linux, x86-64 Linux via the JIT, and macOS — one engine, three runtimes, no VM in any of them.

Faster than emulation

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 →

Real container isolation

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.

The JIT is the kernel

Namespaces, cgroups, image layers and networking are ordinary userspace state — the gVisor / PRoot lineage, with none of a VM's cost.

Desktop app, no root

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

A container should be a process, 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 idleNone — per-container, freed on exitGigabytes for the VM, always on
StartupProcess spawnBoot a Linux VM + the in-VM daemon
Bind-mount / file I/ODirect host filesystem via a path jailvirtiofs/FUSE bridge across the VM
Port publishingStraight to host socketsThrough the VM's NAT layer
Battery / backgroundNothing running when idleA VM idling and draining battery
ObservabilityA normal macOS processAn 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

Native-speed arm64. x86 that laps emulation.

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.

≈1×
arm64 vs native

dd runs arm64 compute at native speed — and beats it on some workloads.

23×
faster x86 compute

float n-body — the same x86 binary vs qemu software emulation. 10 of 11 workloads win.

real openssl crypto

x86 AES-NI/SHA-NI mapped to ARM hardware crypto — where qemu runs it in software.

arm64 — dd vs a native VM

WorkloadVMdddd vs VM
int sieve0.74s0.48s1.55× faster
mandelbrot0.76s0.74s1.03× faster
matrix multiply0.63s0.64s~parity
memcpy0.53s0.54s~parity
base640.65s0.65s~parity
float n-body0.16s0.17s~parity
SHA-2560.77s0.80s~parity
qsort0.79s1.05s1.33× slower
text scan0.49s0.66s1.35× slower
SQLite 600k0.35s0.52s1.48× slower

x86-64 — the same binary, dd vs qemu emulation

Workloadqemudddd vs qemu
float n-body5.43 s0.24 s23× faster
python — int loop10.5 s1.14 s9.3× faster
mandelbrot8.02 s0.87 s9.2× faster
matrix multiply8.27 s1.15 s7.2× faster
sqlite — insert 200k1.47 s0.22 s6.8× faster
openssl AES-128-GCM*640 MB/s3889 MB/s6.1× faster
sqlite — 20k selects424 ms83 ms5.1× faster
memcpy2.42 s0.51 s4.7× faster
qsort3.93 s1.52 s2.6× faster
openssl SHA-256*372 MB/s578 MB/s1.6× faster
base644.33 s5.09 s1.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

dd vs a modern Rosetta VM (OrbStack)

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:

Workloaddd vs OrbStack — arm64dd vs OrbStack — x86 (Rosetta)
container startup1.07× faster2.06× faster — beats Rosetta
redis SET / GET1.43–1.46× faster0.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 / python1.6–1.9× slower1.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

Install once, then it's just Docker

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.

The easy way — ddcli

One 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

…or plain Docker

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

Download dd for macOS

Apple-Silicon Macs (arm64), macOS 12+. A single self-contained app — the UI, the ddcli tool and the runtime. Free and open source (MIT).

Download the latest .dmg

Alpha software — expect rough edges. Not notarized yet, so on first launch right-click ddOpen once; it runs normally after. ddcli doctor can clear it for you.