
Noyau Linux en espace utilisateur basé sur JIT qui exécute les conteneurs nativement sur macOS Apple Silicon sans VM. Remplacement de l'API Docker Engine intégré avec isolation des conteneurs, images overlay et publication de ports.
Exécutez des conteneurs Linux sur macOS — sans machine virtuelle.
dd exécute des conteneurs Linux nativement sur macOS Apple Silicon sans machine virtuelle. Il n'y a pas de noyau Linux ni d'hyperviseur en dessous : un JIT traduit le code du conteneur et prend en charge ses appels système Linux dans l'espace utilisateur (filiation gVisor / PRoot). Le JIT est le noyau Linux de l'invité — les espaces de noms, cgroups, couches d'images overlay et le réseau sont maintenus comme un état dans l'espace utilisateur. Il parle l'API Docker Engine, donc le client docker ordinaire le pilote.
Le calcul du conteneur s'exécute comme des instructions natives Apple Silicon ; seuls ses appels système sont interprétés. Pas de VM à démarrer, pas de démon dans une VM, pas de coût de virtualisation.
Site web & docs : https://ricccrd.github.io/dd/
make jit # build.rs compile et signe les JIT
DD_IMAGES=/path/to/images cargo run -p dd-daemon # démarre le démon
export DOCKER_HOST=unix://$PWD/dd.sock
docker run -p 8080:80 -m 256m alpine sh -c 'echo hi from $(hostname)'
DOCKER_HOST vers son socket et vos commandes docker run / ps / images / build existantes fonctionnent sans modification.jit86) qui décode le x86, synthétise ses flags et abaisse SSE/x87 sur NEON (les binaires glibc fonctionnent) ; et invités macOS arm64 (ddcli mac) — aucune VM dans aucun d'eux..wh. whiteout, getdents fusionné), un VFS de type jail de chemins sans TOCTOU, espaces de noms PID / UTS / USER, un netns loopback privé avec publication de ports -p, et limites de mémoire + pids via cgroups (OOM à la limite).dd installent un démon en arrière-plan par utilisateur et un — tout sous , jamais .Toute autre façon d'exécuter des conteneurs Linux sur un Mac — Docker Desktop, Colima, Rancher, OrbStack — démarre une VM Linux sous un hyperviseur et exécute le démon à l'intérieur. Cette VM est un impôt que vous payez toute la journée. dd la supprime : un conteneur est un simple processus macOS dont les appels système sont traités par un noyau Linux dans l'espace utilisateur.
Le gain est structurel : le calcul de l'invité s'exécute en instructions natives Apple Silicon (aucune couche de virtualisation matérielle dans le chemin critique), et le célèbre goulot d'étranglement du partage de fichiers de Docker Desktop — le pont virtiofs/FUSE entre macOS et la VM — n'existe tout simplement pas, car le VFS de dd est le système de fichiers hôte derrière une jail de chemins.
Compromis honnête : un noyau dans l'espace utilisateur est seulement aussi complet que les appels système qu'il implémente, et aujourd'hui par défaut l'invité s'exécute dans un seul processus — rapide, et le bon choix pour du code auquel vous faites confiance (votre environnement de développement, CI, vos propres outils). Pour du code non fiable, il existe désormais une séparation sentinelle optionnelle (
DDJIT_UNTRUSTED) : l'invité s'exécute dans un sandbox Seatbelt en mode refus-par-défaut ne détenant aucune autorisation hôte fs/réseau, tandis qu'un processus sentinelle de confiance possède les vraies ressources et sert les appels système via un anneau mémoire partagée — la forme gVisor. C'est encore tôt (les appels système fichier de base — read/write/open/close/lseek — sont transférés aujourd'hui ; sockets/exec/fork arrivent), donc pour du code totalement hostile, une VM expose toujours une surface plus restreinte.
Le même binaire Linux statique, exécuté de deux manières sur un Apple M5 Pro (macOS 26.3) : à l'intérieur de la VM Linux (comment les Docker basés sur VM exécutent les conteneurs) vs. via le JIT de dd sur l'hôte sans VM. Médiane de 7 (make bench). Un temps plus bas est meilleur ; « dd vs VM » > 1× signifie que dd est plus rapide. La voie dd paie même un petit coût de pont inter-processus que la vraie application ne paie pas — ce sont donc des chiffres conservateurs.
Conteneurs x86-64 — dd vs émulation VM (qemu-user ; exécuter x86 sur Apple Silicon signifie traduire de toute façon). Le JIT de dd bat qemu sur 9 des 10 charges de travail, de manière spectaculaire sur les calculs en virgule flottante :
Conteneurs aarch64 — dd vs une VM native (la VM exécute arm64 à pleine vitesse native — la barre la plus difficile) :
dd exécute le calcul arm64 à vitesse native — en tête sur le crible entier + mandelbrot, à parité sur SHA-256, matmul, memcpy, n-body et base64. Les écarts restants sont liés aux branchements indirects / appels système — qsort (~1.3×), text-scan (~1.35×) et SQLite (~1.5×) — réduits nettement par les dernières passes (§B-off + vol de x16/x17 a fait passer SQLite de ~1.9× à ~1.5×). Combler le reste (répartition VDBE) est la frontière active ; voir docs/design/arm-sqlite-parity.md. (Chaque charge de travail est dimensionnée pour s'exécuter ≥0.45s, donc le faible coût de pont par passage du banc d'essai est négligeable ici.)
Ce sont des micro-benchmarks de calcul — ils ne capturent même pas les gains structurels de dd (pas de VM à démarrer, pas de RAM résidente, I/O direct sur le système de fichiers hôte). Tous les chiffres mesurés, médiane de 7. Reproduction : make bench.
L'objectif est de battre la VM sur tous les benchmarks. dd gagne déjà sur chaque charge de travail x86-64 ci-dessus et égale ou bat arm64 natif ; là où il est encore en retard — SQLite arm64 lourd en appels système/allocation, et tirer plus du traducteur x86 — c'est exactement la frontière d'optimisation (l'optimiseur de traces de niveau 2 et le travail de performance jit86). La parité ou mieux partout est la barre.
dd exécute un conteneur Linux en étant son noyau dans l'espace utilisateur. Un JIT traduit le code machine de l'invité et intercepte chaque instruction d'appel système ; le gestionnaire d'interception — service() dans dd-jit/src/runtime/os/linux/ — est l'ABI des appels système Linux, implémentée par rapport à l'hôte macOS.
ld.so) et construire la pile initiale.# 1. Démarrer le démon, pointer docker vers lui
make jit
DD_IMAGES=/path/to/images cargo run -p dd-daemon
export DOCKER_HOST=unix://$PWD/dd.sock
# 2. C'est juste 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. Ou via l'application de bureau installée (par utilisateur, sans root)
dd install # LaunchAgent + contexte docker
dd app # ouvrir l'interface graphique
docker --context dd run alpine echo hi
dd cible macOS Apple Silicon (arm64, macOS 12+). Le JIT a besoin des outils en ligne de commande Xcode (clang + codesign).
Prenez le dernier .dmg depuis la page des versions, ouvrez-le et glissez dd dans Applications. Ensuite, dans un terminal :
dd install # arborescence ~/.dd + LaunchAgent par utilisateur + `docker context create dd`
dd app # ouvrir l'interface graphique
dd doctor # vérifier socket / agent / contexte / quarantaine de l'application
Gatekeeper : le DMG n'est pas signé (ad-hoc). Au premier lancement, faites un clic droit sur l'application → Ouvrir, ou exécutez
xattr -dr com.apple.quarantine /Applications/dd-app.app(dd doctordétecte ceci et affiche la correction).
xcode-select --install # clang + codesign
# installer Rust (stable) et Nix (pour le shell de développement GTK4)
git clone https://github.com/ricccrd/dd && cd dd
make app # construire + assembler & signer ad-hoc target/dd-app.app
make dmg # -> target/dist/dd-<ver>-<arch>.dmg
make install # copier dans /Applications et exécuter `dd install`
make app/dmg exécutent le regroupement dans le shell de développement Nix (nix/flake.nix), qui fournit GTK4 + dylibbundler / create-dmg. Le bundle relocalise le graphe de dylibs GTK dans Contents/Frameworks, place les données d'exécution GTK, et signe ad-hoc de l'intérieur vers l'extérieur.
Un espace de travail Cargo.
dd-jit/ — l'environnement d'exécution JIT (C, sous src/runtime/) plus ses liaisons Rust.
build.rs compile et signe un binaire JIT par architecture invitée (aarch64, x86_64) ;
src/lib.rs expose Guest + le contrat de lancement typé SpawnConfig. L'invité aarch64 est entièrement décomposé (moteur jit/ + personnalité os/linux/ + frontal frontend/aarch64/) ; l'invité x86-64 (jit86) partage la couche os/linux/.dd-daemon/ — le démon de l'API Docker Engine. Détecte l'architecture invitée de chaque image à partir de son ELF, choisit le JIT correspondant et le lance via .Le démon écoute sur ~/.dd/run/docker.sock ; l'interface graphique et docker --context dd l'utilisent. L'état persiste dans ~/.dd/state.json.
make test # la matrice moteur × cas, rapport groupé
make test ENGINE=x86_64 # un moteur
make test FILTER=container # un groupe / cas correspondant à un nom
cargo run -p dd-tests -- --list # lister les groupes + cas
make test-ci # le chemin cargo-test (CI)
Les cas sont déclarés dans dd-tests/src/cases/. Un cas est un programme invité + assertions ; les invités aarch64 sont compilés à la volée (gcc -static-pie) et comparés à un oracle natif, les invités x86-64 proviennent d'accessoires préconstruits. Chaque cas s'exécute sur chaque moteur pour lequel il a un invité.
clang + codesign (Xcode CLT).-p), netns loopback privé, limites cgroups mémoire+pids, espaces de noms UTS/PID/USER.docs/ pour les descriptions détaillées.Richard Hutta — [email protected]
MIT.
docker context$HOMEsudo| dd — noyau en espace utilisateur (JIT) | Basé sur VM (Docker Desktop / Colima / …) |
|---|
| Modèle sous-jacent | Un JIT prend en charge les appels système Linux dans l'espace utilisateur (filiation gVisor) | Un noyau Linux complet à l'intérieur d'une VM hyperviseur |
| RAM résidente au repos | Aucune — par conteneur, libérée à la sortie | Gigaoctets réservés pour la VM, toujours allumée |
| Démarrage | Création de processus — pas de VM à démarrer | Démarrer une VM Linux + le démon dans la VM d'abord |
| Montage de volume / I/O fichier | Système de fichiers hôte direct via une jail de chemins | Pont virtiofs/gRPC-FUSE à travers la frontière de la VM |
| Publication de ports | Directement sur les sockets de l'hôte | Via la couche NAT/forwarding de la VM |
| Coût batterie / arrière-plan | Rien qui tourne quand aucun conteneur n'est actif | Une VM au ralenti qui vide la batterie |
| Empreinte à distribuer & corriger | Pas de noyau Linux — rien à suivre pour les CVE | Distribue, corrige et suit tout un noyau Linux |
| Observabilité | Un processus macOS normal — samplez, déboguez, Activity Monitor | Une VM opaque ; la charge de travail est invisible pour les outils de l'hôte |
| Charge de travail | VM (qemu) | dd (sans VM) | dd vs VM |
|---|
| n-body flottant | 5.39s | 0.23s | 24× plus rapide |
| mandelbrot | 7.81s | 0.83s | 9.4× plus rapide |
| matmul | 8.21s | 1.37s | 6.0× plus rapide |
| SQLite (600k lignes) | 2.99s | 1.01s | 3.0× plus rapide |
| qsort | 3.91s | 1.68s | 2.3× plus rapide |
| memcpy | 2.40s | 1.10s | 2.2× plus rapide |
| text-scan (wc/grep) | 1.42s | 1.11s | 1.3× plus rapide |
| crible entier | 1.31s | 1.04s | 1.25× plus rapide |
| SHA-256 | 2.72s | 2.44s | 1.1× plus rapide |
| base64 | 4.28s | 5.39s | 0.79× (1.26× plus lent) |
| Charge de travail | VM (native) | dd (sans VM) | dd vs VM |
|---|
| crible entier | 0.75s | 0.48s | 1.58× plus rapide |
| mandelbrot | 0.79s | 0.77s | 1.03× plus rapide |
| matmul | 0.66s | 0.66s | ~parité |
| memcpy | 0.55s | 0.56s | ~parité |
| base64 | 0.68s | 0.68s | ~parité |
| n-body flottant | 0.17s | 0.17s | ~parité |
| SHA-256 | 0.80s | 0.82s | ~parité |
| qsort | 0.83s | 1.10s | 1.33× plus lent |
| text-scan (wc/grep) | 0.51s | 0.68s | 1.35× plus lent |
| SQLite (600k lignes) | 0.36s | 0.62s | 1.71× plus lent |
SpawnConfigdd-tests/ — un banc d'essai déclaratif ; les cas s'exécutent sur chaque moteur avec un rapport groupé.dd-client/ — un petit client typé de l'API Docker Engine via le socket Unix du démon (la source unique de vérité pour le format filaire, partagée par l'interface graphique et le CLI).dd-gui/ (binaire dd-app) — une interface graphique GTK4. Construit uniquement sur macOS via le shell de développement Nix.dd-cli/ (binaire dd) — la surface d'installation/contrôle, toute sans root.