Voltar às atualizações
New releaseSep 3, 2026

vpod v0.8.1

Sandboxes Linux leves e seguros para processos não confiáveis. Funciona no navegador e no servidor.

Compartilhar

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() em crates/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, clippy e 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.

Categorias