
Sandbox Linux légères et sécurisées pour les processus non fiables. Fonctionne dans le navigateur et sur le serveur.
Vpod vpod ?Un vpod est un sandbox léger et portable qui offre à un processus non fiable un environnement Linux instantané. Il utilise une architecture RISC‑V et s'exécute entièrement dans WebAssembly.
Un vpod exécute un système RISC‑V complet (RV64GC, un seul vCPU) compilé en WebAssembly. À l'intérieur, il démarre un vrai noyau Linux avec un véritable espace utilisateur, de sorte que les shells, outils et démons se comportent comme sur du matériel réel.
Instantanés. Au lieu de démarrer Linux à partir de zéro, un vpod restaure un instantané : un état machine sauvegardé (registres CPU, RAM, système de fichiers) capturé juste après le démarrage. Sa restauration prend bien moins d'une seconde. La suspension fonctionne de la même manière en sens inverse : seules les pages mémoire modifiées sont réécrites sur le disque, ce qui permet de mettre un sandbox en pause et de le reprendre plus tard, même depuis un autre processus.
Traduction à l'avance. L'émulation pure instruction par instruction est lente, et WebAssembly exclut un JIT à l'exécution. Ainsi, lors de la construction de l'instantané, les chemins de code invité les plus sollicités sont traduits du RISC‑V en code natif compilé dans le module WASM lui-même. À l'exécution, l'émulateur bascule vers ces blocs traduits lorsque le code invité correspond, et revient à l'interpréteur dans le cas contraire. Cela représente environ 5x de gain sur les tâches gourmandes en CPU, sans aucun effet sur l'isolation : le code traduit passe par les mêmes contrôles MMU et mémoire que le code interprété.
La frontière WASI. Le composant WASM communique avec l'hôte exclusivement via WASI 0.2. L'invité ne voit jamais les descripteurs de fichiers, sockets ou mémoire de l'hôte : l'accès au système de fichiers passe par des répertoires explicitement montés, et la mise en réseau passe par une pile réseau en mode utilisateur à l'intérieur du composant qui ne demande jamais à l'hôte que des sockets sortantes simples. Tout le reste (noyau invité, processus, mémoire) vit dans la mémoire linéaire WASM et meurt avec elle.
G (Extensions à usage général)
C (Instructions compressées) Réduit la taille du code de 30 %, améliorant la vitesse de récupération des instructions et l'efficacité mémoire. Cela compte lors de l'exécution d'un espace utilisateur Linux complet dans notre environnement WASM à mémoire contrainte.
[!NOTE] L'extension V (vectorielle) n'est pas implémentée. Les instructions RVV s'exécuteraient comme du RISC-V émulé ; il n'y a pas de passage SIMD vers le CPU hôte. L'ajout de V augmenterait la surcharge d'émulation sans aucun bénéfice de performance pour les charges vectorisées.
npm install @capsule-run/vpod
import { Sandbox } from "@capsule-run/vpod";
const sandbox = await Sandbox.create();
// L'état est préservé entre les appels
await sandbox.commands.run("export API_KEY=secret");
const key = await sandbox.commands.run("echo $API_KEY");
console.log(key.stdout); // secret
// REPL Python — les variables persistent
await sandbox.code.run("data = [1, 2, 3]");
const total = await sandbox.code.run("print(sum(data))");
console.log(total.text); // 6
await sandbox.close();
Le même paquet fonctionne dans un onglet de navigateur, où l'instantané est mis en cache dans le stockage privé d'origine plutôt que sur le disque.
[!IMPORTANT] Le premier appel à
Sandbox.create()télécharge l'instantané par défaut (alpine) et le met en cache localement s'il n'est pas déjà présent.
pip install vpod
from vpod import Sandbox
# Exécuter une commande
sandbox = Sandbox.create()
result = sandbox.commands.run("whoami")
print(result.stdout) # root
sandbox.close()
# Session persistante — l'état est préservé entre les appels
with Sandbox.create() as sandbox:
sandbox.commands.run("export API_KEY=secret")
result = sandbox.commands.run("echo $API_KEY")
print(result.stdout) # secret
# REPL Python — les variables persistent
with Sandbox.create() as sandbox:
sandbox.code.run("import requests")
sandbox.code.run("data = [1, 2, 3]")
result = sandbox.code.run("print(sum(data))")
print(result.text) # 6
curl -fsSL https://install.vpod.sh | sh
Ou installer via PowerShell (Windows)
irm https://install.vpod.sh | iex
# Récupérer un instantané
vpod pull alpine:latest
# Démarrer un shell interactif
vpod
Consultez la documentation Vpod.
Les contributions sont les bienvenues, des rapports de bugs au support de nouveaux périphériques. Ouvrez une issue pour discuter de tout élément substantiel avant de le construire.
wasm32-wasip2 : rustup target add wasm32-wasip2# Ponctuel : générer le stub AOT (un nouveau clone n'a pas de blocs traduits)
./scripts/aot-stub.sh
# Construire le composant WASM (bibliothèque + CLI). Copie les deux niveaux dans sdks/python/vpod/
./scripts/build-wasm.sh
# Installer la CLI hôte
cargo install --path crates/vpod
# Installer le SDK Python en mode développement
pip install -e "sdks/python[dev]"
# Construire le SDK TypeScript. Récupère le composant depuis le répertoire du SDK Python
cd sdks/typescript && npm install && npm run build
npm run build utilise par défaut --tier aot ; CI fixe --tier base. La construction refuse un composant plus ancien que le fichier le plus récent sous crates/, donc relancez ./scripts/build-wasm.sh après avoir modifié l'émulateur. Une modification de l'émulateur ne se manifeste qu'à travers l'invité, donc un composant obsolète compile et réussit presque tout.
CI les exécute à chaque PR, donc exécutez-les avant de pousser :
cargo fmt --all -- --check # formatage
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all # tests Rust
# Tests d'intégration du SDK Python (nécessite la bibliothèque WASM en place)
cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
pytest sdks/python/tests/ -v -m integration
# SDK TypeScript (depuis sdks/typescript)
npm run typecheck
npm test # unitaire
npm run test:all # unitaire + intégration, nécessite un instantané local
npm run test:perf # régressions du temps invité, constantes exactes
Les tests TypeScript importent le dist/ construit, pas src/, donc construisez avant de les exécuter. Ils recherchent un instantané dans le répertoire de cache partagé ; VPOD_TEST_SNAPSHOT=/path/to/x.snap les dirige ailleurs.
Pour exercer le navigateur de bout en bout, npm run dev sert la page avec COOP/COEP activés et node dev/run-network.mjs --browser chrome la pilote en mode headless.
Les SDK tirent par défaut depuis registry.vpod.sh. Pour exécuter un instantané que vous avez construit vous-même, fournissez-le directement au lieu d'un nom de registre :
// TypeScript : un fichier sur disque (Node), ou des octets (partout)
await Sandbox.create({ snapshot: { path: "./dist/alpine-3.23.0-256mb.snap" } });
await Sandbox.create({ snapshot: { bytes, name: "alpine-3.23.0-256mb.snap" } });
# Python : VPOD_SNAPSHOT=/path/to/x.snap
Conservez la taille de la RAM dans le nom de fichier dans tous les cas, car l'émulateur la lit à partir de là.
Le projet utilise des instantanés Alpine pré-construits depuis registry.vpod.sh, donc vous n'en avez normalement pas besoin. Pour en construire un localement :
./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh # variante 512 Mo avec numpy/pandas/scipy
[!TIP] Pour utiliser un instantané construit localement dans la CLI, décommentez les lignes dans
resolve_snapshot()danscrates/vpod/src/main.rs.
Les constructions d'instantanés peuvent également exécuter le passage AOT (scripts/aot-snapshot.sh <snapshot>), qui trace une charge de travail représentative, traduit les blocs chauds et reconstruit l'émulateur avec ceux-ci intégrés. Cela prend du temps ; le stub de aot-stub.sh suffit pour le développement quotidien, tout fonctionne de la même manière, juste plus lentement.
Des instantanés personnalisés peuvent également être construits à partir d'un Dockerfile. Le constructeur utilise le CLI container d'Apple sur macOS et Docker Buildx sur Linux. Une nouvelle installation macOS nécessite une configuration unique de son runtime, sinon la construction attend un builder qui ne démarre jamais :
container system kernel set --recommended
container builder start
Sur Linux, installez Docker avec le plugin Buildx et enregistrez l'émulation riscv64 une fois si l'hôte n'est pas déjà configuré pour les constructions multiplateformes :
docker run --privileged --rm tonistiigi/binfmt --install riscv64
./scripts/build-custom-snapshot.sh -f Dockerfile -n my-image # dist/my-image-256mb.snap
# optionnellement : --aot --trace-cmd '<la commande chaude de l'image>' pour intégrer les blocs AOT
Le Dockerfile est construit pour linux/riscv64 (BuildKit exécute les étapes RUN sous émulation), son rootfs aplati remplace le minirootfs Alpine, et le reste du pipeline est identique : overlay vpod, démarrage, --snapshot-save.
Seul le système de fichiers survit à l'exportation. ENV, CMD et ENTRYPOINT de la configuration de l'image sont ignorés, donc persistez l'environnement via /etc/profile.d/ dans une étape RUN. Le démarrage à chaud Python est appliqué automatiquement pour les images basées sur musl qui fournissent python3 dans /usr/bin ou /bin.
fmt, clippy et la suite de tests doivent réussir (CI impose les trois).Ce projet est sous licence Apache License 2.0. Consultez le fichier LICENSE pour plus de détails.