
vpod v0.8.1
Sandboxes Linux leves e seguros para processos não confiáveis. Funciona no navegador e no servidor.
Vpod
O que é um vpod?
Um vpod é um sandbox leve e portátil que dá a um processo não confiável um ambiente Linux instantâneo. Ele usa uma arquitetura RISC‑V e roda inteiramente dentro do WebAssembly.
- Inicialização rápida: Inicializa em menos de um segundo.
- Portátil: Roda em qualquer lugar sem necessidade de configuração.
- Isolado: Todo o estado de execução permanece dentro dos sandboxes WASM.
Como funciona
Um vpod executa um sistema RISC‑V completo (RV64GC, uma única vCPU) compilado para WebAssembly. Dentro dele, um kernel Linux real é inicializado com um userspace real, então shells, ferramentas e daemons se comportam como fariam em hardware real.
Snapshots. Em vez de inicializar o Linux do zero, um vpod restaura um snapshot: um estado de máquina salvo (registradores da CPU, RAM, sistema de arquivos) capturado logo após a inicialização. Restaurar um leva bem menos de um segundo. A suspensão funciona da mesma forma, ao contrário: apenas as páginas de memória sujas são gravadas de volta no disco, então você pode pausar um sandbox e retomá-lo depois, até mesmo de outro processo.
Tradução ahead-of-time. A emulação pura instrução por instrução é lenta, e o WebAssembly descarta um JIT em tempo de execução. Então, no momento da construção do snapshot, os caminhos de código convidado mais quentes são traduzidos de RISC‑V para código nativo que é compilado no próprio módulo WASM. Em tempo de execução, o emulador despacha para esses blocos traduzidos quando o código convidado corresponde, e recorre ao interpretador quando não corresponde. Isso vale aproximadamente 5x em trabalho limitado por CPU, com efeito zero no isolamento: o código traduzido passa pelas mesmas verificações de MMU e memória que o código interpretado.
A fronteira WASI. O componente WASM fala com o host exclusivamente através do WASI 0.2. O convidado nunca vê descritores de arquivo, sockets ou memória do host: o acesso ao sistema de arquivos passa por diretórios explicitamente montados, e a rede passa por uma pilha de rede em modo de usuário dentro do componente que só pede ao host sockets de saída simples. Todo o resto (kernel convidado, processos, memória) vive dentro da memória linear do WASM e morre com ela.
Especificação RV64GC
G (Extensões de propósito geral)
- I: Conjunto de instruções inteiras de 64 bits base.
- M: Multiplicação e divisão por hardware, útil para hashing e criptografia.
- A: Operações atômicas para programas thread-safe.
- F/D: Ponto flutuante de precisão simples e dupla, adequado para computação científica e inferência de ML.
C (Instruções compactas) Reduz o tamanho do código em 30%, melhorando a velocidade de busca de instruções e a eficiência de memória. Isso importa ao executar um userspace Linux completo dentro do nosso ambiente WASM com restrição de memória.
[!NOTE] A extensão V (vetorial) não é implementada. Instruções RVV seriam executadas como RISC-V emulado; não há passagem SIMD para a CPU do host. Adicionar V aumentaria a sobrecarga de emulação sem nenhum benefício de desempenho para cargas de trabalho vetorizadas.
Começando
SDK TypeScript
npm install @capsule-run/vpod
import { Sandbox } from "@capsule-run/vpod";
const sandbox = await Sandbox.create();
// O estado é preservado entre chamadas
await sandbox.commands.run("export API_KEY=secret");
const key = await sandbox.commands.run("echo $API_KEY");
console.log(key.stdout); // secret
// REPL Python — variáveis persistem
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();
O mesmo pacote roda em uma aba do navegador, onde o snapshot é armazenado em cache no armazenamento privado de origem em vez de no disco.
[!IMPORTANT] A primeira chamada a
Sandbox.create()baixa o snapshot padrão (alpine) e o armazena em cache localmente se ainda não estiver presente.
SDK Python
pip install vpod
from vpod import Sandbox
# Executar um comando
sandbox = Sandbox.create()
result = sandbox.commands.run("whoami")
print(result.stdout) # root
sandbox.close()
# Sessão persistente — estado preservado entre chamadas
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 — variáveis persistem
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
Ou instale via PowerShell (windows)
irm https://install.vpod.sh | iex
# Baixar um snapshot
vpod pull alpine:latest
# Iniciar um shell interativo
vpod
Documentação
Visite a documentação do Vpod.
Limitações
- Sobrecarga de emulação: Não há virtualização de hardware dentro do WebAssembly, então todo o código convidado é emulado. A sobrecarga depende inteiramente da carga de trabalho: trabalho limitado por I/O e por rede roda perto da velocidade nativa, enquanto trabalho pesado limitado por CPU roda visivelmente mais lento mesmo com tradução AOT. Se sua carga de trabalho é principalmente "executar uma ferramenta, ler um arquivo, chamar uma API", você não notará.
- Sem acesso a GPU: CUDA, Metal e aceleradores de ML por hardware não estão disponíveis. O suporte pode ser adicionado no futuro com wasi-nn.
Contribuindo
Contribuições são bem-vindas, desde relatórios de bugs até suporte a novos dispositivos. Abra uma issue para discutir qualquer coisa substancial antes de construí-la.
Pré-requisitos
- Rust (última versão estável) com o alvo
wasm32-wasip2:rustup target add wasm32-wasip2 - Python 3.10+ para o SDK Python
- Node 20+ para o SDK TypeScript
- Zig (0.16) e bsdtar, necessários apenas se você construir snapshots você mesmo
Configuração de desenvolvimento
# Única vez: gerar o stub AOT (um clone novo não tem blocos traduzidos)
./scripts/aot-stub.sh
# Construir o componente WASM (biblioteca + CLI). Copia ambas as camadas para sdks/python/vpod/
./scripts/build-wasm.sh
# Instalar o CLI do host
cargo install --path crates/vpod
# Instalar o SDK Python em modo de desenvolvimento
pip install -e "sdks/python[dev]"
# Construir o SDK TypeScript. Pega o componente do diretório do SDK Python
cd sdks/typescript && npm install && npm run build
npm run build usa como padrão --tier aot; o CI fixa --tier base. A construção
recusa um componente mais antigo que o arquivo mais recente em crates/, então execute novamente
./scripts/build-wasm.sh após tocar no emulador. Uma alteração no emulador só
aparece através do convidado, então um componente desatualizado compila e passa em quase
tudo.
Executando testes
O CI executa estes em cada PR, então execute-os antes de enviar:
cargo fmt --all -- --check # formatação
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all # testes Rust
# Testes de integração do SDK Python (precisa da biblioteca WASM no lugar)
cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
pytest sdks/python/tests/ -v -m integration
# SDK TypeScript (a partir de sdks/typescript)
npm run typecheck
npm test # unitário
npm run test:all # unitário + integração, precisa de um snapshot local
npm run test:perf # regressões de tempo do convidado, constantes exatas
Os testes TypeScript importam o dist/ construído, não src/, então construa antes de
executá-los. Eles procuram um snapshot no diretório de cache compartilhado;
VPOD_TEST_SNAPSHOT=/path/to/x.snap os aponta para outro lugar.
Para exercitar o navegador de ponta a ponta, npm run dev serve a página com COOP/COEP
ativados e node dev/run-network.mjs --browser chrome a conduz headless.
Usando um snapshot construído localmente
Os SDKs puxam de registry.vpod.sh por padrão. Para executar um que você mesmo construiu,
entregue-o diretamente em vez de um nome de registro:
// TypeScript: um arquivo no disco (Node), ou bytes (em qualquer 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=/path/to/x.snap
Mantenha o tamanho da RAM no nome do arquivo de qualquer forma, porque o emulador o lê de lá.
Construindo snapshots
O projeto usa snapshots Alpine pré-construídos de registry.vpod.sh, então você normalmente não precisa disso. Para construir um localmente:
./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh # variante de 512 MB com numpy/pandas/scipy
[!TIP] Para usar um snapshot construído localmente no CLI, descomente as linhas em
resolve_snapshot()emcrates/vpod/src/main.rs.
As construções de snapshot também podem executar o passo AOT (scripts/aot-snapshot.sh <snapshot>), que rastreia uma carga de trabalho representativa, traduz os blocos quentes e reconstrói o emulador com eles embutidos. Leva um tempo; o stub de aot-stub.sh é suficiente para o desenvolvimento diário, tudo funciona da mesma forma, apenas mais lento.
A partir de um Dockerfile (macOS e Linux)
Snapshots personalizados também podem ser construídos a partir de um Dockerfile. O construtor usa
o CLI container da Apple no macOS e
Docker Buildx no Linux. Uma instalação nova do macOS precisa que seu runtime seja configurado uma vez,
caso contrário a construção espera por um builder que nunca inicia:
container system kernel set --recommended
container builder start
No Linux, instale o Docker com o plugin Buildx e registre a emulação riscv64 uma vez se o host ainda não estiver configurado para construções 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 '<o comando quente da imagem>' para embutir blocos AOT
O Dockerfile é construído para linux/riscv64 (BuildKit executa passos RUN
sob emulação), seu rootfs achatado substitui o minirootfs Alpine,
e o resto do pipeline é idêntico: overlay vpod, inicialização,
--snapshot-save.
Apenas o sistema de arquivos sobrevive à exportação. ENV, CMD e ENTRYPOINT da
configuração da imagem são descartados, então persista o ambiente através de /etc/profile.d/ em
um passo RUN. O warm-start Python é aplicado automaticamente para imagens baseadas em musl
que fornecem python3 em /usr/bin ou /bin.
Pull requests
- Mantenha os PRs focados: uma alteração por PR.
fmt,clippye a suíte de testes devem passar (o CI aplica os três).- Se você tocar nos caminhos de execução ou memória do emulador, diga como você validou a correção (a suíte de testes no mínimo; para alterações sutis, uma inicialização mais uma carga de trabalho real no convidado é uma boa verificação de sanidade).
Licença
Este projeto é licenciado sob a Apache License 2.0. Consulte o arquivo LICENSE para detalhes.