
Kernel Linux in userspace basato su JIT che esegue contenitori nativamente su macOS con Apple Silicon senza una VM. Sostituto drop-in dell'API di Docker Engine con isolamento dei contenitori, immagini overlay e pubblicazione delle porte.
Esegui container Linux su macOS — senza VM.
dd esegue container Linux in modo nativo su macOS con Apple Silicon senza una macchina virtuale. Non c'è
kernel Linux né hypervisor sottostante: un JIT traduce il codice del container e gestisce le sue
chiamate di sistema Linux nello spazio utente (la discendenza gVisor / PRoot). Il JIT è il kernel Linux dell'ospite —
namespace, cgroups, layer di immagini overlay e rete sono mantenuti come stato dello spazio utente. Parla
l'API Docker Engine, quindi la normale CLI docker lo comanda.
Il calcolo del container viene eseguito come istruzioni native Apple Silicon; solo le sue chiamate di sistema vengono interpretate. Nessuna VM da avviare, nessun demone in una VM, nessun costo di virtualizzazione.
Sito web e documentazione: https://ricccrd.github.io/dd/
make jit # build.rs compiles + codesigns the JITs
DD_IMAGES=/path/to/images cargo run -p dd-daemon # start the daemon
export DOCKER_HOST=unix://$PWD/dd.sock
docker run -p 8080:80 -m 256m alpine sh -c 'echo hi from $(hostname)'
DOCKER_HOST al suo socket e i tuoi comandi docker run / ps / images / build esistenti funzionano invariati.jit86) che decodifica x86, sintetizza i suoi flag e abbassa SSE/x87 su NEON (i binari glibc funzionano); e ospiti macOS arm64 (ddcli mac) — nessuna VM in nessuno di essi..wh. whiteout, getdents uniti), un VFS path-jail senza TOCTOU, namespace PID / UTS / USER, una netns di loopback privata con pubblicazione della porta -p, e limiti cgroup di memoria + pids (OOM al limite).dd installano un demone in background per utente e un — tutto sotto , mai .Ogni altro modo per eseguire container Linux su un Mac — Docker Desktop, Colima, Rancher, OrbStack — avvia una VM Linux sotto un hypervisor e fa funzionare il demone al suo interno. Quella VM è una tassa che paghi tutto il giorno. dd la elimina: un container è un normale processo macOS le cui chiamate di sistema vengono servite da un kernel Linux in spazio utente.
Il vantaggio è strutturale: il calcolo dell'ospite viene eseguito come istruzioni native Apple Silicon (nessun layer di virtualizzazione hardware nel percorso critico), e il famigerato collo di bottiglia della condivisione file di Docker Desktop — il ponte virtiofs/FUSE tra macOS e la VM — semplicemente non esiste, perché il VFS di dd è il filesystem host dietro un path jail.
Compromesso onesto: un kernel in spazio utente è completo solo quanto le chiamate di sistema che implementa, e oggi Per impostazione predefinita, l'ospite viene eseguito in un processo — veloce, e la scelta giusta per codice di cui ti fidi (il tuo ambiente di sviluppo, CI, i tuoi strumenti). Per codice non fidato ora c'è una scissione sentry opzionale (
DDJIT_UNTRUSTED): l'ospite viene eseguito in un sandbox Seatbelt con negazione predefinita senza autorità di filesystem/rete, mentre un processo sentry fidato possiede le risorse reali e serve le chiamate di sistema tramite un anello di memoria condivisa — la forma gVisor. È presto (le chiamate di sistema principali per file — read/write/open/close/lseek — vengono inoltrate oggi; socket/exec/fork stanno arrivando), quindi per codice completamente ostile una VM espone ancora una superficie più ristretta.
Lo stesso binario Linux statico, eseguito in due modi su un Apple M5 Pro (macOS 26.3): dentro la VM Linux (come Docker basato su VM esegue i container) vs. tramite il JIT di dd sull'host con nessuna VM. Mediana di 7 (make bench). Tempo minore è migliore; "dd vs VM" > 1× significa dd è più veloce. La corsia dd paga persino un piccolo costo di ponte cross-process che l'app reale non paga — quindi questi sono conservativi.
Container x86-64 — dd vs emulazione VM (qemu-user; eseguire x86 su Apple Silicon significa tradurlo in ogni caso). Il JIT di dd batte qemu in 9 su 10 carichi di lavoro, in modo drammatico sulla virgola mobile:
Container aarch64 — dd vs una VM nativa (la VM esegue arm64 a piena velocità nativa — la barra più difficile):
dd esegue il calcolo arm64 a velocità nativa — in testa su int sieve + mandelbrot, in parità su SHA-256, matmul, memcpy, n-body e base64. I rimanenti gap sono lavori con molti branch indiretti/chiamate di sistema — qsort (~1.3×), text-scan (~1.35×) e SQLite (~1.5×) — ridotti nettamente dalle ultime ottimizzazioni (§B-off + x16/x17 rubati hanno portato SQLite da ~1.9× a ~1.5×). Chiudere il resto (dispatch VDBE) è la frontiera attiva; vedi docs/design/arm-sqlite-parity.md. (Ogni carico di lavoro è dimensionato per funzionare ≥0.45s, quindi il piccolo costo di ponte per esecuzione del test è trascurabile qui.)
Questi sono micro-benchmark di calcolo — non catturano nemmeno i vantaggi strutturali di dd (nessuna VM da avviare, nessuna RAM residente, I/O diretto sul filesystem host). Tutti i numeri misurati, mediana di 7. Riproduci:
make bench.
L'obiettivo è battere la VM su ogni benchmark. dd vince già ogni carico di lavoro x86-64 sopra e eguaglia o batte arm64 nativo; dove è ancora indietro — SQLite arm64 pesante in chiamate di sistema/allocazioni, e spremere di più dal traduttore x86 — è esattamente la frontiera dell'ottimizzazione (l'ottimizzatore di tracce di livello 2 e il lavoro sulle prestazioni di jit86). Parità o meglio ovunque è il traguardo.
dd esegue un container Linux essendo il suo kernel nello spazio utente. Un JIT traduce il codice macchina dell'ospite e intercetta ogni istruzione di chiamata di sistema; il gestore di intercettazione — service() in dd-jit/src/runtime/os/linux/ — è l'ABI delle chiamate di sistema di Linux, implementata verso l'host macOS.
ld.so) e costruisce lo stack iniziale.# 1. Start the daemon, point docker at it
make jit
DD_IMAGES=/path/to/images cargo run -p dd-daemon
export DOCKER_HOST=unix://$PWD/dd.sock
# 2. It's just 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. Or via the installed desktop app (per-user, no root)
dd install # LaunchAgent + docker context
dd app # open the GUI
docker --context dd run alpine echo hi
dd è pensato per macOS con Apple Silicon (arm64, macOS 12+). Il JIT necessita degli Xcode Command Line Tools (clang + codesign).
Prendi l'ultimo .dmg dalla pagina delle release, aprilo e trascina dd in Applicazioni. Poi in un terminale:
dd install # ~/.dd tree + per-user LaunchAgent + `docker context create dd`
dd app # open the GUI
dd doctor # check socket / agent / context / app quarantine
Gatekeeper: il DMG non è firmato (ad-hoc). Al primo avvio, fai clic destro sull'app → Apri, oppure esegui
xattr -dr com.apple.quarantine /Applications/dd-app.app(dd doctorrileva questo e stampa la correzione).
xcode-select --install # clang + codesign
# install Rust (stable) and Nix (for the GTK4 dev shell)
git clone https://github.com/ricccrd/dd && cd dd
make app # build + assemble & ad-hoc-sign target/dd-app.app
make dmg # -> target/dist/dd-<ver>-<arch>.dmg
make install # copy to /Applications and run `dd install`
make app/dmg eseguono il bundling all'interno del guscio di sviluppo Nix (nix/flake.nix), che fornisce GTK4 + dylibbundler / create-dmg. Il bundle sposta il grafico dylib GTK in Contents/Frameworks, prepara i dati runtime GTK e firma ad-hoc dall'interno verso l'esterno.
Un workspace Cargo.
dd-jit/ — il runtime JIT (C, sotto src/runtime/) più i suoi binding Rust. build.rs compila e firma con codice un binario JIT per architettura ospite (aarch64, x86_64); src/lib.rs espone Guest + il contratto di lancio tipizzato SpawnConfig. L'ospite aarch64 è completamente scomposto (motore jit/ + personalità os/linux/ + frontend aarch64/); l'ospite x86-64 (jit86) condivide il layer os/linux/.dd-daemon/ — il demone dell'API Docker Engine. Rileva l'architettura ospite di ogni immagine dal suo ELF, sceglie il JIT corrispondente e lo lancia tramite .Il demone ascolta su ~/.dd/run/docker.sock; sia la GUI che docker --context dd lo usano. Lo stato persiste in ~/.dd/state.json.
make test # the engine × case matrix, grouped report
make test ENGINE=x86_64 # one engine
make test FILTER=container # one group / cases matching a name
cargo run -p dd-tests -- --list # list groups + cases
make test-ci # the cargo-test path (CI)
I casi sono dichiarati in dd-tests/src/cases/. Un caso è un programma ospite + asserzioni; gli ospiti aarch64 vengono compilati al volo (gcc -static-pie) e confrontati con un oracolo nativo, gli ospiti x86-64 provengono da fixture precompilate. Ogni caso viene eseguito su ogni motore per cui ha un ospite.
clang + codesign (Xcode CLT).-p), netns di loopback privata, limiti cgroup memoria+pids, namespace UTS/PID/USER.docs/ per le descrizioni dettagliate.Richard Hutta — [email protected]
MIT.
docker context$HOMEsudo| dd — kernel in spazio utente (JIT) | Docker basato su VM (Desktop / Colima / …) |
|---|
| Modello sottostante | Un JIT gestisce le chiamate di sistema Linux nello spazio utente (discendenza gVisor) | Un kernel Linux completo all'interno di una VM con hypervisor |
| RAM residente quando inattivo | Nessuna — per container, liberata all'uscita | Gigabyte riservati per la VM, sempre accesa |
| Avvio | Creazione di un processo — nessuna VM da avviare | Avviare prima una VM Linux + il demone dentro la VM |
| Bind-mount / I/O file | Filesystem host diretto attraverso un path jail | Ponte virtiofs/gRPC-FUSE attraverso il confine della VM |
| Pubblicazione porta | Direttamente ai socket host | Attraverso il layer NAT/forwarding della VM |
| Costo batteria / background | Niente in esecuzione quando nessun container è attivo | Una VM inattiva che consuma batteria |
| Impronta da distribuire e aggiornare | Nessun kernel Linux — niente da tracciare per CVE | Distribuisce, aggiorna e traccia un intero kernel Linux |
| Osservabilità | Un normale processo macOS — sample, debug, Activity Monitor | Una VM opaca; il carico di lavoro è invisibile agli strumenti host |
| Carico di lavoro | VM (qemu) | dd (no VM) | dd vs VM |
|---|
| float n-body | 5.39s | 0.23s | 24× più veloce |
| mandelbrot | 7.81s | 0.83s | 9.4× più veloce |
| matmul | 8.21s | 1.37s | 6.0× più veloce |
| SQLite (600k righe) | 2.99s | 1.01s | 3.0× più veloce |
| qsort | 3.91s | 1.68s | 2.3× più veloce |
| memcpy | 2.40s | 1.10s | 2.2× più veloce |
| text-scan (wc/grep) | 1.42s | 1.11s | 1.3× più veloce |
| int sieve | 1.31s | 1.04s | 1.25× più veloce |
| SHA-256 | 2.72s | 2.44s | 1.1× più veloce |
| base64 | 4.28s | 5.39s | 0.79× (1.26× più lento) |
| Carico di lavoro | VM (nativa) | dd (no VM) | dd vs VM |
|---|
| int sieve | 0.75s | 0.48s | 1.58× più veloce |
| mandelbrot | 0.79s | 0.77s | 1.03× più veloce |
| matmul | 0.66s | 0.66s | ~parità |
| memcpy | 0.55s | 0.56s | ~parità |
| base64 | 0.68s | 0.68s | ~parità |
| float n-body | 0.17s | 0.17s | ~parità |
| SHA-256 | 0.80s | 0.82s | ~parità |
| qsort | 0.83s | 1.10s | 1.33× più lento |
| text-scan (wc/grep) | 0.51s | 0.68s | 1.35× più lento |
| SQLite (600k righe) | 0.36s | 0.62s | 1.71× più lento |
SpawnConfigdd-tests/ — un harness di test dichiarativo; i casi vengono eseguiti su ogni motore con un report raggruppato.dd-client/ — un piccolo client tipizzato dell'API Docker Engine sul socket Unix del demone (la singola fonte di verità per il formato del wire, condivisa da GUI e CLI).dd-gui/ (binario dd-app) — un'interfaccia utente desktop GTK4. Compilato solo su macOS tramite il guscio di sviluppo Nix.dd-cli/ (binario dd) — la superficie di installazione/controllo, tutto senza root.