
JIT-basierter Userspace-Linux-Kernel, der Container nativ auf Apple Silicon macOS ohne VM ausführt. Drop-in Docker Engine API-Ersatz mit Container-Isolation, Overlay-Images und Portveröffentlichung.
Linux-Container auf macOS ausführen – ohne VM.
dd führt Linux-Container nativ auf Apple-Silicon macOS ohne virtuelle Maschine aus. Es gibt keinen
Linux-Kernel und keinen Hypervisor darunter: Ein JIT übersetzt den Code des Containers und bedient seine
Linux-Systemaufrufe im Userspace (die gVisor-/PRoot-Linie). Der JIT ist der Linux-Kernel des Gasts –
Namespaces, Cgroups, Overlay-Image-Layer und Netzwerk werden als Userspace-Zustand verwaltet. Er spricht
die Docker Engine API, sodass die normale docker-CLI ihn steuert.
Die Berechnung des Containers läuft als native Apple-Silicon-Instruktionen; nur seine Systemaufrufe werden interpretiert. Keine VM zum Booten, kein Daemon-in-einer-VM, keine Virtualisierungskosten.
Website & Dokumentation: https://ricccrd.github.io/dd/
make jit # build.rs kompiliert + codesignt die JITs
DD_IMAGES=/path/to/images cargo run -p dd-daemon # starte den Daemon
export DOCKER_HOST=unix://$PWD/dd.sock
docker run -p 8080:80 -m 256m alpine sh -c 'echo hi from $(hostname)'
DOCKER_HOST auf seinen Socket und deine
vorhandenen docker run / ps / images / build-Befehle funktionieren unverändert.jit86), der x86 dekodiert, seine Flags synthetisiert und SSE/x87 auf NEON herunterbricht (glibc-Binaries laufen);
und macOS arm64-Gäste (ddcli mac) – in keinem davon eine VM..wh.-Whiteout, gemergte getdents),
ein TOCTOU-freies Path-Jail-VFS, PID-/UTS-/USER-Namespaces, ein privates Loopback-Netns mit -p-Port-
Veröffentlichung und Cgroup-Speicher- + PIDs-Begrenzungen (OOM an der Grenze).dd-CLI installieren einen
benutzerbezogenen Hintergrund-Daemon und einen – alles unter , niemals .Jede andere Methode, Linux-Container auf einem Mac auszuführen – Docker Desktop, Colima, Rancher, OrbStack – bootet eine Linux-VM unter einem Hypervisor und betreibt den Daemon innerhalb dieser VM. Diese VM ist eine Steuer, die du den ganzen Tag bezahlst. dd entfernt sie: Ein Container ist ein normaler macOS-Prozess, dessen Systemaufrufe zufällig von einem Userspace-Linux-Kernel bedient werden.
Der Gewinn ist strukturell: Die Berechnung des Gasts läuft als native Apple-Silicon-Instruktionen (keine
Hardwarevirtualisierungsschicht im heißen Pfad), und der berüchtigte Docker-Desktop-Dateifreigabe-Engpass –
die virtiofs/FUSE-Brücke zwischen macOS und der VM – existiert einfach nicht, weil das VFS von dd das Host-Dateisystem
hinter einem Path-Jail ist.
Ehrlicher Kompromiss: Ein Userspace-Kernel ist nur so vollständig wie die Systemaufrufe, die er implementiert, und heute läuft der Gast standardmäßig in einem Prozess – schnell und die richtige Wahl für Code, dem du vertraust (deine Entwicklungs- umgebung, CI, deine eigenen Tools). Für nicht vertrauenswürdigen Code gibt es jetzt eine optionale Sentry-Aufteilung (
DDJIT_UNTRUSTED): Der Gast läuft in einer Deny-Default-Seatbelt-Sandbox ohne Host-FS-/Netzberechtigung, während ein vertrauenswürdiger Sentry-Prozess die echten Ressourcen besitzt und Systemaufrufe über einen Shared-Memory- Ring bedient – die gVisor-Form. Es ist früh (die Kern-Dateisystemaufrufe – read/write/open/close/lseek – werden heute weitergeleitet; Sockets/exec/fork kommen), daher legt eine VM für vollständig feindlichen Code immer noch eine schmalere Oberfläche frei.
Das gleiche statische Linux-Binary, zweimal ausgeführt auf einem Apple M5 Pro (macOS 26.3): innerhalb der Linux-VM
(wie VM-basiertes Docker Container ausführt) vs. durch dd's JIT auf dem Host ohne VM. Median von 7
(make bench). Niedrigere Zeit ist besser; „dd vs VM“ > 1× bedeutet, dd ist schneller. Die dd-Spur bezahlt sogar eine kleine
Cross-Prozess-Brückensteuer, die die echte Anwendung nicht hat – also sind diese konservativ.
x86-64-Container – dd vs. VM-Emulation (qemu-user; x86 auf Apple Silicon ausführen bedeutet Übersetzung in beiden Fällen). dd's JIT schlägt qemu in 9 von 10 Workloads, dramatisch bei Fließkomma:
aarch64-Container – dd vs. native VM (die VM läuft arm64 mit voller nativer Geschwindigkeit – die härteste Hürde):
dd führt arm64 Berechnung mit nativer Geschwindigkeit aus – vorne bei int sieve + Mandelbrot, bei Parität bei SHA-256, Matmul,
Memcpy, N-Body und Base64. Die verbleibenden Lücken betreffen indirekte Verzweigungen / systemaufruflastige Arbeit – Qsort (~1,3×),
Text-Scan (~1,35×) und SQLite (~1,5×) – deutlich verringert durch die letzten Durchgänge (§B-off + gestohlenes x16/x17 brachte
SQLite von ~1,9× auf ~1,5×). Die verbleibende Lücke (VDBE-Dispatch) zu schließen ist die aktive Grenze; siehe
docs/design/arm-sqlite-parity.md. (Jeder
Workload ist so dimensioniert, dass er ≥0,45 s läuft, sodass die kleine Pro-Lauf-Brückensteuer hier vernachlässigbar ist.)
Dies sind Berechnungs-Mikrobenchmarks – sie erfassen nicht einmal dd's strukturelle Gewinne (keine VM zum Booten, kein
residenter RAM, direktes Host-Dateisystem-I/O). Alle Zahlen gemessen, Median von 7. Reproduzierbar: make bench.
Das Ziel ist, die VM in jedem Benchmark zu schlagen. dd gewinnt bereits jeden x86-64-Workload oben und erreicht Parität oder übertrifft natives arm64; wo es noch zurückliegt – systemaufruf-/allokationslastiges arm64 SQLite, und mehr aus dem x86-Übersetzer herauszuholen – ist genau die Optimierungsgrenze (der Tier-2-Trace-Optimierer und die Jit86-Perf-Arbeit). Parität oder besser überall ist die Messlatte.
dd führt einen Linux-Container aus, indem es im Userspace sein Kernel ist. Ein JIT übersetzt den Maschinencode des Gasts
und fängt jede Systemaufruf-Instruktion ab; der Fallback-Handler – service() in dd-jit/src/runtime/os/linux/ –
ist die Linux-Systemaufruf-ABI, implementiert gegen den macOS-Host.
ld.so) und Aufbau des initialen Stacks.# 1. Starte den Daemon, weise docker darauf hin
make jit
DD_IMAGES=/path/to/images cargo run -p dd-daemon
export DOCKER_HOST=unix://$PWD/dd.sock
# 2. Es ist einfach Docker
docker run -p 8080:80 -m 256m alpine sh -c 'echo hi from $(hostname)'
docker ps
docker images
docker run --rm -it ubuntu bash
# 3. Oder über die installierte Desktop-App (benutzerbezogen, kein Root)
dd install # LaunchAgent + docker context
dd app # öffne die GUI
docker --context dd run alpine echo hi
dd zielt auf Apple-Silicon macOS (arm64, macOS 12+). Der JIT benötigt die Xcode Command Line Tools
(clang + codesign).
Hole die neueste .dmg von der Releases-Seite,
öffne sie und ziehe dd in Programme. Dann in einem Terminal:
dd install # ~/.dd-Baum + benutzerbezogener LaunchAgent + `docker context create dd`
dd app # öffne die GUI
dd doctor # überprüfe Socket / Agent / Kontext / App-Quarantäne
Gatekeeper: Die DMG ist unsigniert (ad-hoc). Beim ersten Start rechtsklicke die App → Öffnen, oder führe
xattr -dr com.apple.quarantine /Applications/dd-app.appaus (dd doctorerkennt dies und gibt die Korrektur aus).
xcode-select --install # clang + codesign
# Installiere Rust (stable) und Nix (für die GTK4-Entwicklungsshell)
git clone https://github.com/ricccrd/dd && cd dd
make app # baue + assembliere & ad-hoc-signiere target/dd-app.app
make dmg # -> target/dist/dd-<ver>-<arch>.dmg
make install # kopiere nach /Applications und führe `dd install` aus
make app/dmg führen das Bündeln innerhalb der Nix-Entwicklungsshell aus (nix/flake.nix),
die GTK4 + dylibbundler / create-dmg bereitstellt. Das Bundle verschiebt den GTK-dylib-Graphen in
Contents/Frameworks, bereitet die GTK-Laufzeitdaten vor und signiert ad-hoc von innen nach außen.
Ein Cargo-Arbeitsbereich.
dd-jit/ – die JIT-Laufzeit (C, unter src/runtime/) plus ihre Rust-Bindungen.
build.rs kompiliert und codesignt ein JIT-Binary pro Gast-Architektur (aarch64, x86_64);
src/lib.rs stellt Guest + den typisierten SpawnConfig-Startvertrag bereit. Der aarch64-Gast ist vollständig
zerlegt (jit/-Engine + os/linux/-Persönlichkeit + frontend/aarch64/); der x86-64-Gast (jit86)
teilt die os/linux/-Schicht.dd-daemon/ – der Docker-Engine-API-Daemon. Erkennt die Gast-Architektur jedes Images aus seinem ELF,
wählt den passenden JIT aus und startet ihn über .Der Daemon hört auf ~/.dd/run/docker.sock; sowohl die GUI als auch docker --context dd nutzen ihn. Zustand
wird in ~/.dd/state.json gespeichert.
make test # die Engine × Case-Matrix, gruppierter Bericht
make test ENGINE=x86_64 # eine Engine
make test FILTER=container # eine Gruppe / Fälle, die einem Namen entsprechen
cargo run -p dd-tests -- --list # liste Gruppen + Fälle auf
make test-ci # der cargo-test-Pfad (CI)
Fälle werden in dd-tests/src/cases/ deklariert. Ein Fall ist ein Gastprogramm + Zusicherungen; aarch64-Gäste werden
ad hoc kompiliert (gcc -static-pie) und mit einem nativen Orakel difffiert, x86-64-Gäste stammen aus
vorgefertigten Fixtures. Jeder Fall läuft auf jeder Engine, für die er einen Gast hat.
clang + codesign (Xcode CLT).-p), privates Loopback-Netns, Cgroup-Speicher+PIDs-Limits, UTS/PID/USER-Namespaces.docs/ für die detaillierten Ausarbeitungen.Richard Hutta — [email protected]
MIT.
docker context$HOMEsudo| dd – Userspace-Kernel (JIT) | VM-basiertes Docker (Desktop / Colima / …) |
|---|
| Zugrundeliegendes Modell | Ein JIT bedient Linux-Systemaufrufe im Userspace (gVisor-Linie) | Ein vollständiger Linux-Kernel in einer Hypervisor-VM |
| Resident RAM im Leerlauf | Kein – pro Container, beim Beenden freigegeben | Gigabytes für die VM reserviert, immer aktiv |
| Startzeit | Prozess starten – keine VM zum Booten | Erst eine Linux-VM + den Daemon in der VM booten |
| Bind-Mount / Datei-I/O | Direktes Host-Dateisystem durch ein Path-Jail | virtiofs/gRPC-FUSE-Brücke über die VM-Grenze |
| Port-Veröffentlichung | Direkt zu Host-Sockets | Über die NAT-/Forwarding-Schicht der VM |
| Akku-/Hintergrundkosten | Nichts läuft, wenn kein Container aktiv ist | Eine VM, die im Leerlauf läuft und den Akku leert |
| Größe zum Ausliefern & Patchen | Kein Linux-Kernel – nichts zu CVE-verfolgen | Liefert, patcht und verfolgt einen ganzen Linux-Kernel |
| Beobachtbarkeit | Ein normaler macOS-Prozess – sample, debugge, Aktivitätsüberwachung | Eine undurchsichtige VM; die Arbeitslast ist für Host-Tools unsichtbar |
| Workload | VM (qemu) | dd (keine VM) | dd vs VM |
|---|
| float n-body | 5,39 s | 0,23 s | 24× schneller |
| Mandelbrot | 7,81 s | 0,83 s | 9,4× schneller |
| Matmul | 8,21 s | 1,37 s | 6,0× schneller |
| SQLite (600k Zeilen) | 2,99 s | 1,01 s | 3,0× schneller |
| Qsort | 3,91 s | 1,68 s | 2,3× schneller |
| Memcpy | 2,40 s | 1,10 s | 2,2× schneller |
| Text-Scan (wc/grep) | 1,42 s | 1,11 s | 1,3× schneller |
| int sieve | 1,31 s | 1,04 s | 1,25× schneller |
| SHA-256 | 2,72 s | 2,44 s | 1,1× schneller |
| Base64 | 4,28 s | 5,39 s | 0,79× (1,26× langsamer) |
| Workload | VM (nativ) | dd (keine VM) | dd vs VM |
|---|
| int sieve | 0,75 s | 0,48 s | 1,58× schneller |
| Mandelbrot | 0,79 s | 0,77 s | 1,03× schneller |
| Matmul | 0,66 s | 0,66 s | ~Parität |
| Memcpy | 0,55 s | 0,56 s | ~Parität |
| Base64 | 0,68 s | 0,68 s | ~Parität |
| float n-body | 0,17 s | 0,17 s | ~Parität |
| SHA-256 | 0,80 s | 0,82 s | ~Parität |
| Qsort | 0,83 s | 1,10 s | 1,33× langsamer |
| Text-Scan (wc/grep) | 0,51 s | 0,68 s | 1,35× langsamer |
| SQLite (600k Zeilen) | 0,36 s | 0,62 s | 1,71× langsamer |
SpawnConfigdd-tests/ – ein deklaratives Test-Harness; Fälle laufen über jede Engine mit einem gruppierten Bericht.dd-client/ – ein kleiner typisierter Docker-Engine-API-Client über den Unix-Socket des Daemons (die einzige
Quelle der Wahrheit für das Drahtformat, geteilt von GUI und CLI).dd-gui/ (Binary dd-app) – eine GTK4-Desktop-Oberfläche. Nur auf macOS über die Nix-Entwicklungsshell gebaut.dd-cli/ (Binary dd) – die Install-/Steuerungsoberfläche, alles ohne Root.