
vpod v0.8.1
Sandboxes de Linux ligeros y seguros para procesos no confiables. Se ejecuta en el navegador y en el servidor.
Vpod
¿Qué es un vpod?
Un vpod es un sandbox ligero y portátil que proporciona a un proceso no confiable un entorno Linux instantáneo. Utiliza una arquitectura RISC‑V y se ejecuta completamente dentro de WebAssembly.
- Inicio rápido: Arranca en menos de un segundo.
- Portátil: Se ejecuta en cualquier lugar sin necesidad de configuración.
- Aislado: Todo el estado de ejecución permanece dentro de los sandboxes WASM.
Cómo funciona
Un vpod ejecuta un sistema RISC‑V completo (RV64GC, una sola vCPU) compilado a WebAssembly. Dentro arranca un kernel Linux real con un userspace real, por lo que shells, herramientas y demonios se comportan como lo harían en hardware real.
Instantáneas. En lugar de arrancar Linux desde cero, un vpod restaura una instantánea: un estado de máquina guardado (registros de CPU, RAM, sistema de archivos) capturado justo después del arranque. Restaurar una toma mucho menos de un segundo. La suspensión funciona de la misma manera en sentido inverso; solo las páginas de memoria sucias se escriben de vuelta al disco, por lo que puedes pausar un sandbox y reanudarlo más tarde, incluso desde otro proceso.
Traducción anticipada (AOT). La emulación pura instrucción por instrucción es lenta, y WebAssembly descarta un JIT en tiempo de ejecución. Por eso, al compilar la instantánea, las rutas de código invitado más calientes se traducen de RISC‑V a código nativo que se compila dentro del propio módulo WASM. En tiempo de ejecución, el emulador despacha a estos bloques traducidos cuando el código invitado coincide, y recurre al intérprete cuando no coincide. Esto supone aproximadamente 5x en trabajo con uso intensivo de CPU, sin ningún efecto sobre el aislamiento: el código traducido pasa por las mismas comprobaciones de MMU y memoria que el código interpretado.
El límite WASI. El componente WASM se comunica con el host exclusivamente a través de WASI 0.2. El invitado nunca ve descriptores de archivo, sockets o memoria del host: el acceso al sistema de archivos pasa por directorios montados explícitamente, y la red pasa por una pila de red en modo usuario dentro del componente que solo solicita al host sockets de salida simples. Todo lo demás (kernel invitado, procesos, memoria) vive dentro de la memoria lineal WASM y muere con ella.
Especificación RV64GC
G (Extensiones de propósito general)
- I: Conjunto de instrucciones enteras base de 64 bits.
- M: Multiplicación y división por hardware, útil para hash y criptografía.
- A: Operaciones atómicas para programas seguros con hilos.
- F/D: Punto flotante de precisión simple y doble, adecuado para computación científica e inferencia de ML.
C (Instrucciones comprimidas) Reduce el tamaño del código en un 30%, mejorando la velocidad de captura de instrucciones y la eficiencia de memoria. Esto importa al ejecutar un userspace Linux completo dentro de nuestro entorno WASM con restricciones de memoria.
[!NOTE] La extensión V (vectorial) no está implementada. Las instrucciones RVV se ejecutarían como RISC-V emulado; no hay paso directo de SIMD a la CPU del host. Añadir V aumentaría la sobrecarga de emulación sin ningún beneficio de rendimiento para cargas de trabajo vectorizadas.
Primeros pasos
SDK de TypeScript
npm install @capsule-run/vpod
import { Sandbox } from "@capsule-run/vpod";
const sandbox = await Sandbox.create();
// El estado se conserva entre llamadas
await sandbox.commands.run("export API_KEY=secret");
const key = await sandbox.commands.run("echo $API_KEY");
console.log(key.stdout); // secret
// REPL de Python — las variables persisten
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();
El mismo paquete se ejecuta en una pestaña del navegador, donde la instantánea se almacena en caché en almacenamiento privado de origen en lugar de en disco.
[!IMPORTANT] La primera llamada a
Sandbox.create()descarga la instantánea predeterminada (alpine) y la almacena en caché localmente si aún no está presente.
SDK de Python
pip install vpod
from vpod import Sandbox
# Ejecutar un comando
sandbox = Sandbox.create()
result = sandbox.commands.run("whoami")
print(result.stdout) # root
sandbox.close()
# Sesión persistente — el estado se conserva entre llamadas
with Sandbox.create() as sandbox:
sandbox.commands.run("export API_KEY=secret")
result = sandbox.commands.run("echo $API_KEY")
print(result.stdout) # secret
# REPL de Python — las variables persisten
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
CLI
curl -fsSL https://install.vpod.sh | sh
O instalar mediante PowerShell (Windows)
irm https://install.vpod.sh | iex
# Descargar una instantánea
vpod pull alpine:latest
# Iniciar un shell interactivo
vpod
Documentación
Visita la documentación de Vpod.
Limitaciones
- Sobrecarga de emulación: No hay virtualización por hardware dentro de WebAssembly, por lo que todo el código invitado se emula. La sobrecarga depende completamente de la carga de trabajo: el trabajo limitado por E/S y por red se ejecuta cerca de la velocidad nativa, mientras que el trabajo pesado limitado por CPU se ejecuta notablemente más lento incluso con traducción AOT. Si tu carga de trabajo consiste principalmente en "ejecutar una herramienta, leer un archivo, llamar a una API", no lo notarás.
- Sin acceso a GPU: CUDA, Metal y aceleradores de hardware ML no están disponibles. El soporte podría añadirse en el futuro con wasi-nn.
Contribuir
Las contribuciones son bienvenidas, desde informes de errores hasta soporte para nuevos dispositivos. Abre un issue para discutir cualquier cosa sustancial antes de construirla.
Requisitos previos
- Rust (última versión estable) con el target
wasm32-wasip2:rustup target add wasm32-wasip2 - Python 3.10+ para el SDK de Python
- Node 20+ para el SDK de TypeScript
- Zig (0.16) y bsdtar, solo necesarios si compilas instantáneas tú mismo
Configuración de desarrollo
# Una sola vez: generar el stub AOT (un clon nuevo no tiene bloques traducidos)
./scripts/aot-stub.sh
# Compilar el componente WASM (biblioteca + CLI). Copia ambos niveles en sdks/python/vpod/
./scripts/build-wasm.sh
# Instalar la CLI del host
cargo install --path crates/vpod
# Instalar el SDK de Python en modo desarrollo
pip install -e "sdks/python[dev]"
# Compilar el SDK de TypeScript. Toma el componente del directorio del SDK de Python
cd sdks/typescript && npm install && npm run build
npm run build usa por defecto --tier aot; CI fija --tier base. La compilación
rechaza un componente más antiguo que el archivo más reciente en crates/, así que
vuelve a ejecutar ./scripts/build-wasm.sh después de tocar el emulador. Un cambio
en el emulador solo se manifiesta a través del invitado, por lo que un componente
obsoleto compila y pasa casi todo.
Ejecutar pruebas
CI ejecuta estas en cada PR, así que ejecútalas antes de hacer push:
cargo fmt --all -- --check # formato
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all # pruebas de Rust
# Pruebas de integración del SDK de Python (necesita la biblioteca WASM en su lugar)
cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
pytest sdks/python/tests/ -v -m integration
# SDK de TypeScript (desde sdks/typescript)
npm run typecheck
npm test # unitarias
npm run test:all # unitarias + integración, necesita una instantánea local
npm run test:perf # regresiones de tiempo de invitado, constantes exactas
Las pruebas de TypeScript importan el dist/ compilado, no src/, así que compila
antes de ejecutarlas. Buscan una instantánea en el directorio de caché compartido;
VPOD_TEST_SNAPSHOT=/ruta/a/x.snap las apunta a otro lugar.
Para ejercitar el navegador de extremo a extremo, npm run dev sirve la página con
COOP/COEP activados y node dev/run-network.mjs --browser chrome la controla en modo headless.
Usar una instantánea compilada localmente
Los SDKs descargan de registry.vpod.sh por defecto. Para ejecutar una que hayas
compilado tú mismo, entrégala directamente en lugar de un nombre de registro:
// TypeScript: un archivo en disco (Node), o bytes (en cualquier lugar)
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=/ruta/a/x.snap
Mantén el tamaño de RAM en el nombre del archivo de cualquier manera, porque el emulador lo lee de ahí.
Compilar instantáneas
El proyecto usa instantáneas Alpine precompiladas de registry.vpod.sh, por lo que
normalmente no necesitas esto. Para compilar una localmente:
./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh # variante de 512 MB con numpy/pandas/scipy
[!TIP] Para usar una instantánea compilada localmente en la CLI, descomenta las líneas en
resolve_snapshot()encrates/vpod/src/main.rs.
Las compilaciones de instantáneas también pueden ejecutar el paso AOT (scripts/aot-snapshot.sh <instantánea>), que traza una carga de trabajo representativa, traduce los bloques calientes y reconstruye el emulador con ellos integrados. Tarda un tiempo; el stub de aot-stub.sh es suficiente para el desarrollo diario, todo funciona igual, solo que más lento.
Desde un Dockerfile (macOS y Linux)
Las instantáneas personalizadas también se pueden compilar desde un Dockerfile. El compilador usa
la CLI container de Apple en macOS y
Docker Buildx en Linux. Una instalación nueva de macOS necesita configurar su runtime una vez;
de lo contrario, la compilación espera a un builder que nunca arranca:
container system kernel set --recommended
container builder start
En Linux, instala Docker con el plugin Buildx y registra la emulación riscv64 una vez si el host aún no está configurado para compilaciones multiplataforma:
docker run --privileged --rm tonistiigi/binfmt --install riscv64
./scripts/build-custom-snapshot.sh -f Dockerfile -n my-image # dist/my-image-256mb.snap
# opcionalmente: --aot --trace-cmd '<el comando caliente de la imagen>' para integrar bloques AOT
El Dockerfile se compila para linux/riscv64 (BuildKit ejecuta los pasos RUN
bajo emulación), su rootfs aplanado reemplaza el minirootfs de Alpine,
y el resto del pipeline es idéntico: overlay de vpod, arranque,
--snapshot-save.
Solo el sistema de archivos sobrevive a la exportación. ENV, CMD y ENTRYPOINT de
la configuración de la imagen se descartan, así que persiste el entorno mediante /etc/profile.d/ en
un paso RUN. El arranque en caliente de Python se aplica automáticamente para imágenes basadas en musl
que incluyen python3 en /usr/bin o /bin.
Pull requests
- Mantén los PR enfocados: un cambio por PR.
fmt,clippyy la suite de pruebas deben pasar (CI los exige todos).- Si tocas las rutas de ejecución o memoria del emulador, indica cómo validaste la corrección (la suite de pruebas como mínimo; para cambios sutiles, un arranque más una carga de trabajo real en el invitado es una buena comprobación de cordura).
Licencia
Este proyecto está licenciado bajo la Apache License 2.0. Consulta el archivo LICENSE para más detalles.