Rode qualquer código.
Nada sai da caixa.
Cada execução ganha uma MicroVM própria: kernel separado, sem rede, disco só leitura, de pé em ~260 ms e destruída no fim. Precisa de mais? Uma máquina persistente com rede filtrada por host, credenciais que o código usa sem ler, branch instantâneo e um executável portátil da máquina inteira.
Uma máquina nova por execução. Nenhuma sobra.
Um contêiner divide o kernel com o seu computador. Aqui cada execução ganha um computador virtual inteiro, que só existe pelo tempo dela.
Por baixo: libkrun como monitor de MicroVMsHypervisor.framework no macOS, KVM no Linuximagens das linguagens das OCI oficiais, sem Docker
Escolha um recurso e veja a sessão.
Cada cena abaixo é a saída de uma VM real, capturada em 20/10/2026 num Mac M1 Pro por task site:demos. O tempo à direita de cada comando é o medido. Nenhuma linha foi escrita à mão.
runtimeClassName: rc no k3s e no microk8s: cada pod ganha kernel próprio e o IP do CNI, com kubectl exec, logs, Service e DNS do cluster. 19 de 19 checagens nos dois.
dockerd num ext4 de verdade, contêiner aninhado, docker.sock no host.
Vulkan (Venus) até o Metal: 56,7 tokens/s na GPU contra 10,3 na CPU.
🖥️Tela no navegadorA tela da máquina no browser, com teclado e mouse, a partir de 127.0.0.1.
Máquinas se acham por db.lab.internal; outra rede nem resolve.
AWS, R2 ou MinIO montado pelo guest, leitura sob demanda.
🔑Agente SSH e segredosgit push com as chaves do host, que nunca entram na VM.
--expose-socket e --mount-socket: socket Unix nas duas direções.
Só biblioteca padrão. Job, máquina, exec e streaming. Go: go get runner.codes/pkg/client.
Print, clique, teclado e rolagem pela CLI, pela API e pelos SDKs, sem nada instalado na VM.
🤖Agente opera a VMrc mcp: o Claude Code tira print, clica e digita na tela da máquina.
O que dá para montar com isso.
Cada caso abaixo foi rodado numa VM real, com os comandos que aparecem ao lado.
Um agente sem freio, numa caixa.
O Claude Code roda dentro de uma MicroVM com --dangerously-skip-permissions: ele edita, instala e executa sem pedir confirmação, porque o que der errado fica na VM.
- Seu projeto montado em
/workspace: o que o agente escreve aparece na sua pasta. - Terminal no navegador, só em
127.0.0.1e com token na URL. - Qualquer porta que ele abrir (uvicorn, Vite, um banco) vira uma aba com a app dentro, ao lado das abas
shelleeventos. - Suas chaves SSH pelo agente do host:
git pushfunciona, a chave privada nunca entra. - Login uma vez: a sessão fica no disco da máquina e sobrevive ao stop.
rc machine create --name agent --image node:22 --net -e IS_SANDBOX=1 \ --init "npm install -g @anthropic-ai/claude-code" rc machine start --name agent rc machine web-terminal --name agent -- claude --dangerously-skip-permissions
▐▛███▛█ Claude Code v2.1.304 ▝▜██████▀ Opus 5.5 · Claude Max ▝▝ ▝▝ /tmp/demo ❯ Write a tiny Python web server that answers "hello from the playground" on port 8000, start it in the background and check it with curl. Ran 2 shell commands ● curl isn't installed here, so I'll check the server with Python's HTTP client instead. Ran 1 shell command ● The server is running in the background on port 8000 and answers hello from the playground. I couldn't check it with curl because curl isn't installed in this environment. Instead I fetched the page with Python's built-in HTTP client and got 200 text/plain with that text. - Code: /tmp/demo/server.py uses only Python's standard library and answers any GET request. - Log: /tmp/demo/server.log records each request. To check it with curl yourself, install it first (for example ! apt-get install -y curl), then run curl localhost:8000. To stop the server, run pkill -f server.py, or kill its process ID if pkill isn't available either (pgrep wasn't). ✻ Churned for 15s · done 4:18 PM ──────────────────────────────────────────────────────────────────────────────────────────────────── ❯ install curl and check it ──────────────────────────────────────────────────────────────────────────────────────────────────── ⏵⏵ bypass permissions on (shift+tab to cycle)
Uma máquina sua, agora, no navegador.
Em claude-in-chrome.runner.codes cada visitante ganha uma máquina própria com o Claude Code aberto sem confirmação de permissão.
- Sua conta: o
/loginé seu e fica só na sua máquina. - Qualquer porta que você subir responde em
https://<porta>-<id>.runner.codes/e vira uma aba. - Isolada: internet aberta, rede privada do provedor fechada.
- Temporária: desliga após 30 min parada e é apagada após 24 h sem uso; no máximo 3 ao mesmo tempo.
https://8000-<id>.runner.codes/.Rode o código dos seus usuários.
Exercícios, correção automática, sandbox de documentação: cada envio roda numa MicroVM nova, sem rede, e some no fim.
- 15 linguagens pela mesma rota.
- Pool já aquecido: 26–43 ms por requisição.
- Saída ao vivo por SSE, e timeout que mata a VM inteira.
rc serve --listen 127.0.0.1:8080 --pool-size 2
curl -s -X POST http://127.0.0.1:8080/api/v1/run \
-H 'content-type: application/json' \
-d '{"language":"python","stdin":"oi","files":[{"name":"main.py","content":"print(input().upper())"}]}'
{"status":"success","stdout":"OI\n","stderr":null,"exception":null,"executionTime":40}Um LLM local, isolado, na GPU do Mac.
llama.cpp com Vulkan dentro da VM, falando com a GPU do host por virtio-gpu (Venus → MoltenVK → Metal).
- 56,7 tokens/s na GPU contra 10,3 na CPU (Qwen2.5 0.5B).
- Sem acesso ao seu disco: nenhum diretório do host é montado, a menos que você declare um volume.
rc image build --stack llama rc run --stack llama --gpu --code 'vulkaninfo --summary | grep deviceName'
Um desktop com Claude Code numa aba.
XFCE, Firefox e Claude Code num runner.code, com a tela no navegador. A tela acompanha a janela, e um agente opera a VM pelo rc mcp: print, clique e teclado.
- Só em 127.0.0.1, sem VNC nem cliente extra.
- Start em 5,6 s a partir de um
rc packdo desktop pronto.
cd examples/desktop-claude && rc machine create --name desk
CLAUDE_CODE_OAUTH_TOKEN=... rc machine start --name desk
claude mcp add rc -- rc mcp --name desk
# a tela fica em http://127.0.0.1:6080Você decide o que o código alcança.
Uma máquina nasce sem rede e sem nada do seu disco. Cada abertura é uma linha declarada. Marque o que precisa e veja o runner.code, o comando equivalente e o que muda para o código lá dentro.
runner.code
o mesmo pela linha de comando
o que o código dentro da máquina alcança
Para rodar código que você não escreveu.
Código gerado por IA, enviado por um usuário ou baixado da internet roda sem acesso aos seus arquivos, à sua rede e ao seu sistema.
Código gerado por IA
Execute o que o LLM escreveu sem revisar linha por linha antes. Se der errado, dá errado dentro da MicroVM.
Máquina para agentes
Claude Code ou opencode com o repositório montado, rede só para a API do modelo, a chave como segredo e o SSH do host. Faça um branch antes de cada tentativa.
Playgrounds e avaliações
Rode código de usuários em 15 linguagens pela API, com pool já aquecido: 26–43 ms por requisição.
Mais forte que um contêiner
Um contêiner só separa processos dentro do mesmo sistema. A MicroVM é outro computador: o que der errado lá não alcança o seu.
Quando não usar: se o código é seu, é confiável e precisa de rede, um contêiner resolve e é mais simples.
| Contêiner | runner.codes | |
|---|---|---|
| Kernel | o mesmo do host | próprio, um por máquina |
| Rede | ligada por padrão | desligada; aberta host a host, com log de cada recusa |
| Credenciais | variável legível pelo processo | placeholder; o valor real entra no host, só para o host permitido |
| Disco | gravável | job: root só leitura; máquina: disco próprio com branch copy-on-write |
| Fim do job | o processo morre | a MicroVM inteira some |
Um job, uma máquina ou uma API.
Job efêmero
MicroVM nova, executa, devolve stdout e stderr, e some. O jeito mais direto de rodar um trecho de código.
- stdout e stderr aparecem enquanto o programa roda
rc shellabre um shell interativo numa MicroVM descartável
# roda e devolve a saída rc run --stack python --code 'print(2**10)' 1024 # shell interativo numa MicroVM que some no exit rc shell --stack python
Máquina persistente
Disco gravável que sobrevive entre um start e outro, de uma stack ou de qualquer imagem OCI. Declarada num arquivo runner.code, versionado junto com o código.
create,start,exec,shell,cp,logs,stop,deletebranch,pause,resume,sync,update(rede com a máquina ligada)rc pack create --from-vmvira um executável
rc machine create --name dev --stack python --allow-host pypi.org rc machine start --name dev rc machine exec --name dev -- pip install --user requests rc machine branch --from dev --name tentativa-1 rc machine shell --name tentativa-1 rc machine stop --name dev
API HTTP
Um pool de MicroVMs já iniciadas por linguagem, cada uma usada uma vez: a requisição leva 26–43 ms. Saída em streaming (SSE) e as máquinas inteiras pela API.
- sem autenticação: por isso escuta só em
127.0.0.1por padrão - campo desconhecido no JSON é recusado, não ignorado
rc serve --listen 127.0.0.1:8080 --pool-size 2
curl -s -X POST http://127.0.0.1:8080/api/v1/run \
-H 'content-type: application/json' \
-d '{"language":"python","stdin":"oi","files":[{"name":"main.py","content":"print(input().upper())"}]}'
{"status":"success","stdout":"OI\n","stderr":null,"exception":null,"executionTime":40}Teste você mesmo, passo a passo.
O mesmo roteiro que task runbook executa e confere sozinho: 146 conferências, 0 falhas na última rodada. Copie os comandos de cada passo, compare com o esperado e marque. O progresso fica salvo neste navegador.
Sequência para conferir num terminal novo, um comando por vez, que o rc faz o que promete. Cada passo diz o que você deve ver.
Este runbook é executável. task runbook (scripts/runbook.sh) roda exatamente estes passos e confere cada resultado esperado. Ele reprova se algum sair diferente. Regra do projeto: todo recurso novo ganha uma seção aqui e o passo correspondente no scripts/runbook.sh, na mesma entrega.
| Última execução | Host | Resultado |
|---|---|---|
| 20/10/2026 | macOS 27, Apple M1 Pro | task runbook: 0 falhas em 116 conferências (até o 8u: usuário, segredos, pontes de socket, ping, staged, recusas) |
| 19/10/2026 | macOS 27, Apple M1 Pro | task runbook: 0 falhas em 50 conferências (com volumes e rede, passos 8c e 8d). Prova de falha: com o DNS do gateway sem filtro, o 8d reprovou em 2 conferências |
Pré-requisitos: task krun:install, task build e as imagens (./bin/rc image build --all; o passo 9 precisa da llama).
Rode com o e2e parado: os testes conferem com pgrep que nenhuma VM sobrou, e uma máquina sua ligada faz eles reprovarem.
0Preparar
cd ~/works/labs/github/runner.codes ./bin/rc doctor
Esperado: ok em platform, libkrun, gpu e guest, e uma linha ok image <stack> por imagem construída.
1Job efêmero com saída em streaming
./bin/rc run --stack python --code $'import time\nprint("early")\ntime.sleep(1.5)\nprint("late")'Esperado: early aparece logo (~0,3 s, o boot) e late 1,5 s depois, enquanto o programa ainda roda. No fim, [status=success exit=0 ...] no stderr. A VM é destruída ao terminar.
2Criar e ligar uma máquina
./bin/rc machine create --name teste --stack python ./bin/rc machine start --name teste ./bin/rc machine ls
Esperado:
created teste (stack=python cpus=4 memory=8192MiB disk=10GiB gpu=false);started teste (pid N) in ~200ms;testecomrunningnols.
A máquina continua ligada depois que o comando sai.
3Gravar coisas em três lugares
./bin/rc machine exec --name teste -- sh -c 'echo persistiu > /etc/minha-config; echo dados > /workspace/arquivo.txt; echo some > /tmp/temporario.txt'
Esperado: nada na saída, e rc 0.
4Parar e ligar de novo
./bin/rc machine stop --name teste ./bin/rc machine start --name teste
Esperado: stopped teste e started teste (pid N) in ~200ms. O stop faz sync do disco antes de desligar.
5Conferir o que ficou
./bin/rc machine exec --name teste -- sh -c 'cat /etc/minha-config /workspace/arquivo.txt; cat /tmp/temporario.txt'
Esperado:
persistiu dados cat: can't open '/tmp/temporario.txt': No such file or directory
/etc (camada gravável sobre a imagem) e /workspace (disco da máquina) persistem; /tmp é tmpfs e volta vazio.
6Copiar um arquivo para a máquina e de volta
head -c 20000000 /dev/urandom > /tmp/rc-up.bin ./bin/rc machine cp /tmp/rc-up.bin teste:/workspace/up.bin ./bin/rc machine exec --name teste -- sha256sum /workspace/up.bin shasum -a 256 /tmp/rc-up.bin ./bin/rc machine cp teste:/workspace/up.bin /tmp/rc-down.bin cmp /tmp/rc-up.bin /tmp/rc-down.bin && echo "idênticos"
Esperado:
uploaded 20000000 bytes ... in ~230ms;- o mesmo sha256 dentro e fora da máquina;
downloaded ... in ~230ms;idênticos.
7Código de saída e streaming dentro da máquina
./bin/rc machine exec --name teste -- sh -c 'echo a; sleep 1; echo b; exit 7'; echo "rc=$?"
Esperado: a, b um segundo depois e rc=7. O rc do rc é o do comando.
7bTerminal interativo na máquina
./bin/rc machine shell --name teste
Dentro do shell que abrir, digite:
test -t 0 && echo IS-TTY; stty size # IS-TTY e o tamanho do seu terminal sleep 30 # aperte Ctrl+C: interrompe só o sleep echo ALIVE # o shell continua vivo exit 4
Fora, echo $? mostra 4. A máquina continua ligada: o shell é uma sessão, não a VM.
Esperado: terminal de verdade, com Ctrl+C, setas, histórico, vi e redimensionamento da janela. É bash se a imagem tiver; na python (Alpine) é o sh.
8Logs da máquina
./bin/rc machine logs --name teste --tail 10
Esperado: as últimas linhas do console (o guest registra cada operação). Funciona também com a máquina parada.
8bMáquina declarada num runner.code
O runner.code é a máquina declarada num arquivo: stack ou imagem, recursos, env, init e workload num TOML versionável.
mkdir -p /tmp/rc-demo && cd /tmp/rc-demo cat > runner.code <<'TOML' stack = "python" cpus = 2 env = ["GREETING=hello"] workdir = "/app" init = ["echo $(( $(cat count 2>/dev/null || echo 0) + 1 )) > count"] entrypoint = ["sh", "-c"] cmd = ["trap 'echo saved > saved; exit 0' INT; echo \"up $GREETING in $(pwd)\"; while :; do sleep 0.2; done"] stop_signal = "SIGINT" stop_timeout = 5 TOML ~/works/labs/github/runner.codes/bin/rc machine create --name web --cpus 1 cd ~/works/labs/github/runner.codes ./bin/rc machine status --name web --json ./bin/rc machine start --name web ./bin/rc machine logs --name web --source workload ./bin/rc machine exec --name web -- sh -c 'cat count; echo $GREETING; pwd' ./bin/rc machine stop --name web ./bin/rc machine start --name web ./bin/rc machine exec --name web -- sh -c 'cat count saved' ./bin/rc machine stop --name web ./bin/rc machine delete --name web
Esperado:
- o
createsem-fdizfrom runner.code, e o status mostra"cpus": 1: a flag vence o arquivo; logs --source workloadmostraup hello in /app: o workdir foi criado e env e workdir chegaram ao workload;- o primeiro
execimprime1,hello,/app; - o segundo imprime
1esaved: o init não rodou de novo no segundo start, e oSIGINTdo stop chegou ao workload, que gravou o arquivo antes do sync.
Chave que o runner.code ainda não implementa é recusada, nunca ignorada:
printf 'stack = "python"\n[health]\nexec = ["true"]\n' > /tmp/rc-demo/bad.code ./bin/rc machine create --name x -f /tmp/rc-demo/bad.code
Esperado: key "health" is not supported yet: health checks, e nada criado.
8cVolumes: diretório do host dentro da máquina
mkdir -p /tmp/rc-vol/ro && echo "do host" > /tmp/rc-vol/hello.txt ./bin/rc machine create --name vol --stack python -v /tmp/rc-vol:/data -v /tmp/rc-vol/ro:/ro:ro -w /data ./bin/rc machine start --name vol ./bin/rc machine exec --name vol -- sh -c 'cat hello.txt; echo do-guest > from-guest.txt; echo y > /ro/y; echo ro=$?' cat /tmp/rc-vol/from-guest.txt ./bin/rc machine stop --name vol ./bin/rc machine delete --name vol
Esperado:
do host: o guest lê o diretório do host;Read-only file systemero=1: o:roé imposto pela libkrun no device, e o root do guest não o desfaz;- no host,
catmostrado-guest: a escrita do guest aparece no host na hora.
8dRede: saída, política e porta
Máquina nasce sem rede. --net liga a saída; --allow-host liga e restringe; -p publica uma porta no 127.0.0.1 do host.
./bin/rc machine create --name net --stack python --allow-host example.com -p 18080:8000 ./bin/rc machine start --name net ./bin/rc machine exec --name net -- sh -c 'wget -qO /dev/null http://example.com; echo ok=$?' ./bin/rc machine exec --name net -- sh -c 'wget -qO /dev/null http://iana.org' ./bin/rc machine exec --name net -- sh -c 'wget -qO- -T 3 http://169.254.169.254; echo meta=$?' ./bin/rc machine update --name net --allow-host iana.org ./bin/rc machine exec --name net -- sh -c 'wget -qO /dev/null http://iana.org; echo ok=$?' ./bin/rc machine exec --name net -- sh -c 'cd /tmp && echo do-guest > i.html && (python3 -m http.server 8000 >/dev/null 2>&1 &)' curl -s http://127.0.0.1:18080/i.html cat ~/Library/Caches/runner-codes/machines/net/egress-denials.log ./bin/rc machine stop --name net ./bin/rc machine delete --name net
Esperado:
ok=0paraexample.com;wget: bad address 'iana.org'na hora: o DNS do gateway responde NXDOMAIN, sem esperar timeout;meta=1: a metadata de nuvem (169.254.169.254) é recusada mesmo com a rede aberta;- depois do
update,iana.orgpassa sem reiniciar a máquina; curlno host recebedo-guestpela porta publicada;- o log de recusas tem
deny dns iana.org: name not in the allow list.
8eMáquina efêmera: machine run
./bin/rc machine run --stack python -- python3 -c 'print("ola"); raise SystemExit(3)'; echo "rc=$?"
echo dados | ./bin/rc machine run -i --stack python -- sh -c 'read x; echo leu:$x'
./bin/rc machine run --net --image alpine -- uname -a
./bin/rc machine run -it --image alpine -- /bin/sh # terminal; `exit` sai e apaga a máquina
./bin/rc machine lsEsperado:
olaerc=3: o rc é o do comando;leu:dados: o-irepassa o stdin (sem-io stdin não é lido);- o
uname -ado kernel da libkrunfw numa imagem fora do catálogo (construída no primeiro uso); - o
lssem nenhumarun-…: a máquina efêmera some no fim, inclusive no Ctrl+C.
8fPorta publicada alcança servidor que só escuta em localhost
./bin/rc machine run -d --name web --stack python -p 18080:9000 -- python3 -m http.server 9000 --bind 127.0.0.1
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18080/
./bin/rc machine delete --name web -fEsperado: 200. A porta publicada entra pelo guest (op dial no vsock) e disca o 127.0.0.1 de lá, então o servidor que escuta só em localhost, como o vite ou o openclaw, responde.
8gTela no navegador: display, teclado e mouse
./bin/rc machine create --name tela --image alpine:latest --gpu --net --display 1024x768
./bin/rc machine start --name tela
./bin/rc machine exec --name tela -- sh -c 'apk add -q --no-cache libdrm-tests evtest; cat /sys/class/input/event*/device/name'
./bin/rc machine exec --name tela -- sh -c 'c=$(modetest -M virtio_gpu -c | awk "/connected/ {print \$1; exit}"); (modetest -M virtio_gpu -s $c:1024x768 >/dev/null 2>&1 </dev/null &)'
open http://localhost:6080
./bin/rc machine exec --name tela -- sh -c '(timeout 25 evtest /dev/input/event0 > /tmp/k.log 2>&1 </dev/null &)'
# clique na página e digite algumas letras; depois:
./bin/rc machine exec --name tela -- grep 'value 1' /tmp/k.log
./bin/rc machine delete --name tela -fEsperado:
- o guest lista
runner.code keyboarderunner.code tablet; - a página mostra as barras de cor do
modetest, ao vivo, em 1024x768; - cada tecla digitada na página aparece no
evtestdo guest (KEY_R,KEY_C...).
O task runbook confere o mesmo caminho sem precisar de olho humano: o quadro de 1024x768 chega ao visualizador e um KEY_A mandado por POST /input aparece no evtest.
8hTela responsiva e controle de tela
A mesma máquina com tela do 8g, agora controlada pela CLI, pela API e pelo MCP, e com a resolução pedida pela página:
./bin/rc machine create --name tela --image alpine:latest --gpu --net --display 1024x768
./bin/rc machine start --name tela
./bin/rc machine exec --name tela -- sh -c 'apk add -q --no-cache libdrm-tests evtest; mkdir -p /etc/rc; printf "#!/bin/sh\necho \"\$1 \$2\" > /tmp/resize.txt\n" > /etc/rc/display-resize; chmod 755 /etc/rc/display-resize'
curl -s -X POST localhost:6080/resize -d '{"width":900,"height":600}'
./bin/rc machine exec --name tela -- cat /tmp/resize.txt
./bin/rc machine exec --name tela -- sh -c 'c=$(modetest -M virtio_gpu -c | awk "/connected/ {print \$1; exit}"); (sleep 90 | modetest -M virtio_gpu -s $c:1024x768 >/dev/null 2>&1 &)'
./bin/rc machine screen --name tela screenshot -o tela.jpg
./bin/rc machine exec --name tela -- sh -c '(timeout 8 evtest /dev/input/event0 > /tmp/k.log &); (timeout 8 evtest /dev/input/event1 > /tmp/t.log &)'
./bin/rc machine screen --name tela key ctrl+c
./bin/rc machine screen --name tela click 1023 767
./bin/rc machine exec --name tela -- sh -c 'sleep 8; grep -h "value 1" /tmp/k.log /tmp/t.log | head'
./bin/rc machine screen --name tela key hyper
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | ./bin/rc mcp --name tela
./bin/rc machine delete --name tela -fEsperado:
900 600no/tmp/resize.txt: o tamanho que a página pede chega ao gancho/etc/rc/display-resizedo guest (no desktop, ele rodaxrandr; sem o gancho, nada acontece);screen 1024x768 -> tela.jpg, um JPEG da tela;- no
evtest:KEY_LEFTCTRLeKEY_Capertados,ABS_Xem32767(o canto direito) eBTN_LEFT; - a página do visualizador desenha uma seta laranja onde a CLI clicou (o
GET /pointerregistra oclick); - a página da tela traz
runner.codesna barra (odisplay_titledorunner.codetroca o nome); key hypersai com erro (unknown key "hyper"), sem mandar nada à máquina;- o
rc mcpresponde aoinitializee lista 9 ferramentas:screenshot,click,move,drag,type,key,scroll,actionseexec.
O task runbook faz o mesmo e confere também a API: GET /api/v1/machines/:name/screen com X-Screen-Width: 1024, 400 para ação inválida e 204 para válida em POST .../screen/actions.
8iBranch e pausa
./bin/rc machine create --name mom --stack python && ./bin/rc machine start --name mom ./bin/rc machine exec --name mom -- sh -c 'echo antes > /root/marca' ./bin/rc machine pause --name mom && ./bin/rc machine status --name mom ./bin/rc machine branch --from mom --name kid ./bin/rc machine resume --name mom ./bin/rc machine start --name kid && ./bin/rc machine exec --name kid -- cat /root/marca ./bin/rc machine delete --name kid -f && ./bin/rc machine delete --name mom -f
Esperado:
statusmostrapaused, e umexecnesse estado é recusado com o motivo;- a filha mostra
antes: o branch leva o que a mãe gravou, mesmo com ela pausada (orca retoma por um instante para osync); - depois disso, mãe e filha são independentes.
8jRede nomeada
./bin/rc machine run -d --name db --stack python --network lab -- python3 -m http.server 8080 ./bin/rc machine create --name app --stack python --network lab && ./bin/rc machine start --name app ./bin/rc machine exec --name app -- wget -qO- -T 3 http://100.98.0.10:8080/ ./bin/rc machine exec --name app -- wget -qO- -T 3 http://db.lab.internal:8080/ ./bin/rc machine delete --name db -f ./bin/rc machine exec --name app -- nslookup db.lab.internal ./bin/rc machine delete --name app -f
Esperado:
- as duas máquinas recebem IPs seguidos da rede (
dbé100.98.0.10,appé100.98.0.11) e se alcançam pelo IP e pelo nome<máquina>.<rede>.internal, sem--net: tráfego dentro da rede não é saída; - máquina de outra rede não alcança essas, nem resolve os nomes delas;
dbapagada some do DNS da rede: onslookupnão devolve mais o IP dela.
8kVolume S3
Um S3 local, servindo uma pasta (o MinIO saiu do nixpkgs em 11/10/2026):
mkdir -p /tmp/s3/bkt/data && echo 'ola do bucket' > /tmp/s3/bkt/data/hello.txt nix shell nixpkgs#rclone -c rclone serve s3 /tmp/s3 --addr 127.0.0.1:19000 --auth-key chave,segredo123 & ./bin/rc machine create --name s3 --stack python --net --allow-cidr 127.0.0.1/32 \ -e AWS_ACCESS_KEY_ID=chave -e AWS_SECRET_ACCESS_KEY=segredo123 -e AWS_ENDPOINT_URL=http://100.96.0.1:19000 \ -v s3://bkt/data:/data ./bin/rc machine start --name s3 ./bin/rc machine exec --name s3 -- sh -c 'cat /data/hello.txt; echo do-guest > /data/novo.txt' cat /tmp/s3/bkt/data/novo.txt ./bin/rc machine delete --name s3 -f
Esperado:
- o
startmonta o bucket (com chave errada, ele falha coms3: 403e o motivo); - o guest lê
ola do bucket, e onovo.txtaparece na pasta do servidor comdo-guest.
8lCredencial que a máquina usa sem ler
./bin/rc machine create --name cred --stack python --credential tok=RB_TOKEN@postman-echo.com
RB_TOKEN=valor-do-runbook ./bin/rc machine start --name cred
./bin/rc machine exec --name cred -- python3 -c '
import json, os, urllib.request
tok = os.environ["RB_TOKEN"]
print("guest:", tok)
r = urllib.request.Request("https://postman-echo.com/headers", headers={"Authorization": "Bearer " + tok, "User-Agent": "runbook/1"})
print("server:", json.load(urllib.request.urlopen(r, timeout=20))["headers"]["authorization"])'
./bin/rc machine delete --name cred -fEsperado:
guest: RC_PLACEHOLDER_TOK_...: o guest só tem o placeholder;server: Bearer valor-do-runbook: o servidor recebeu o valor real, que veio do ambiente dostart.
O User-Agent é próprio porque o Cloudflare do postman-echo recusa o Python-urllib (403, error code 1010).
8mPack: a máquina num arquivo
./bin/rc machine create --name pk --stack python && ./bin/rc machine start --name pk ./bin/rc machine exec --name pk -- sh -c 'echo marca-do-runbook > /root/marca' ./bin/rc machine stop --name pk ./bin/rc pack create --from-vm pk --output /tmp/one --single-file ./bin/rc machine delete --name pk /tmp/one run -- cat /root/marca /tmp/one
Esperado:
marca-do-runbook: o artefato levou o disco da máquina, mesmo com a origem apagada (um pack que perdesse o disco ainda ligaria e sairia 0; por isso se confere um valor);/tmp/onesozinho mostrausage: one run ....
8nUsuário, volume de disco e docker.sock
./bin/rc machine create --name usr --stack python --user nobody
./bin/rc machine start --name usr
./bin/rc machine exec --name usr -- id -un
./bin/rc machine exec --name usr --user root -- id -un
./bin/rc machine create --name dk --stack python --docker-socket -v /var/lib/dados -- python3 -c '
import os, socket
s = socket.socket(socket.AF_UNIX); s.bind("/var/run/docker.sock"); s.listen()
while True:
c, _ = s.accept(); c.sendall(b"do-guest:" + c.recv(64)); c.close()'
./bin/rc machine start --name dk
./bin/rc machine exec --name dk -- stat -f -c %T /var/lib/dados
python3 -c 'import socket,sys; s=socket.socket(socket.AF_UNIX); s.connect(sys.argv[1]); s.sendall(b"ping"); print(s.recv(64).decode())' "$(./bin/rc machine data-dir --name dk)/docker.sock"Esperado:
nobody, erootcom--user root(o--userdo exec vence o da máquina);ext2/ext3: o volume de disco é ext4 de verdade, fora do overlay (o que o overlay2 do Docker precisa);do-guest:ping: o host fala com o servidor do guest pelodocker.sockdo diretório da máquina.
8oSegredos e agente SSH
ssh-keygen -q -t ed25519 -N "" -f /tmp/rb-id && ssh-agent -D -a /tmp/rb-agent.sock & sleep 1 SSH_AUTH_SOCK=/tmp/rb-agent.sock ssh-add -q /tmp/rb-id echo do-arquivo > /tmp/rb-sec.txt ./bin/rc machine create --name sec --image alpine --net --init 'apk add -q --no-cache openssh-client' \ --secret-env API=RB_SECRET --secret-file ARQ=/tmp/rb-sec.txt --ssh-agent RB_SECRET=valor-secreto SSH_AUTH_SOCK=/tmp/rb-agent.sock ./bin/rc machine start --name sec ./bin/rc machine exec --name sec -- sh -c 'echo $API $ARQ; ssh-add -l' grep -rl valor-secreto --exclude='*.ext4' "$(./bin/rc machine data-dir --name sec)"
Esperado:
valor-secreto do-arquivo: os dois segredos no ambiente de todo comando;ssh-add -llista a chave de/tmp/rb-id(a privada fica no agente do host);- o
grepnão acha nada: o valor não fica no registro da máquina.
O agente e a chave são descartáveis, deste passo: nada toca no agente de quem roda.
8pPontes de socket nas duas direções
python3 -c 'import socket; s=socket.socket(socket.AF_UNIX); s.bind("/tmp/rb-h.sock"); s.listen()
while True:
c,_=s.accept(); c.sendall(b"do-host:"+c.recv(64)); c.close()' &
./bin/rc machine create --name skt --stack python --expose-socket /run/app.sock --mount-socket /tmp/rb-h.sock:/run/h/h.sock -- python3 -c '
import socket
s = socket.socket(socket.AF_UNIX); s.bind("/run/app.sock"); s.listen()
while True:
c, _ = s.accept(); c.sendall(b"do-guest:" + c.recv(64)); c.close()'
./bin/rc machine start --name skt
python3 -c 'import socket,sys; s=socket.socket(socket.AF_UNIX); s.connect(sys.argv[1]); s.sendall(b"ping"); print(s.recv(64).decode())' "$(./bin/rc machine data-dir --name skt)/app.sock"
./bin/rc machine exec --name skt -- python3 -c 'import socket; s=socket.socket(socket.AF_UNIX); s.connect("/run/h/h.sock"); s.sendall(b"pong"); print(s.recv(64).decode())'Esperado: do-guest:ping (o host alcança o socket que o workload escuta) e do-host:pong (o guest alcança o socket do host, num diretório que a imagem não tinha).
8qPing e faixa de portas
./bin/rc machine create --name png --stack python --net -p 18301-18303:18301-18303 -- python3 -m http.server 18302
./bin/rc machine start --name png
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18302/
./bin/rc machine exec --name png -- ping -c1 -W3 1.1.1.1
./bin/rc machine create --name pngd --stack python --allow-cidr 198.51.100.0/24
./bin/rc machine start --name pngd
./bin/rc machine exec --name pngd -- ping -c1 -W2 1.1.1.1
grep 'deny icmp' "$(./bin/rc machine data-dir --name pngd)/egress-denials.log"Esperado:
200: a faixa publica uma porta por número (até 64), e a do meio responde;- o ping a
1.1.1.1volta com a rede aberta, e só se o host recebeu a resposta (não há resposta inventada); - fora da allowlist o ping não volta, e a negação
deny icmp 1.1.1.1vai para o log.
8rVolume staged e sync
mkdir -p /tmp/rb-repo && echo original > /tmp/rb-repo/a.txt && echo x > /tmp/rb-repo/old.txt ./bin/rc machine create --name stg --stack python -v /tmp/rb-repo:/work:staged ./bin/rc machine start --name stg ./bin/rc machine exec --name stg -- sh -c 'stat -f -c %T /work; echo mudou > /work/a.txt; rm /work/old.txt; printf "#!/bin/sh\n" > /work/run.sh; chmod 755 /work/run.sh' cat /tmp/rb-repo/a.txt ./bin/rc machine sync --name stg cat /tmp/rb-repo/a.txt; ls /tmp/rb-repo; ls -l /tmp/rb-repo/run.sh
Esperado:
ext2/ext3: a cópia está no disco da máquina, sem ida e volta de virtio-fs;- antes do sync o host continua
original; - depois:
mudou, semold.txt(o sync espelha: o que sumiu da cópia some do host) erun.shcom-rwxr-xr-x.
O stop gracioso e o fim do machine run também sincronizam; --timeout 0 não. A filha de um branch não devolve nada ao host.
8sRecusas que dizem o motivo
./bin/rc doctor | grep -E 'cuda|nested' ./bin/rc machine create --name x --image alpine --cuda time ./bin/rc machine run --nested --image alpine -- ls -l /dev/kvm
Esperado:
info cuda: not supported (no CUDA remoting; --gpu is Vulkan)e a linha do nested deste host;--cuda is not supported: rc has no CUDA remoting ..., sem criar máquina;- num host que não aninha (M1/M2),
failed to start: Error: nested virtualization is not supported on this host ...em menos de 1 s (antes: 30 s edid not become ready); num host que aninha,/dev/kvmdentro da VM.
8tDiretório da máquina e limpeza do cache
ls "$(./bin/rc machine data-dir --name usr)" ./bin/rc image prune ./bin/rc pack prune
Esperado: machine.json no diretório da máquina; os dois prunes saem 0 (sem --all, o image prune só apaga o ext4 para o qual nenhum nome aponta mais, o build anterior de uma imagem, e o pack prune guarda os 5 usados mais recentemente).
8uOs guias
task guides DIRS=gpu-cuda # uma pasta; sem DIRS, todas (cada uma liga as suas VMs)
Esperado: falhas: 0 em cada pasta. O que cada pasta prova está em examples/guides/README.md.
8vTerminal web
./bin/rc machine web-terminal --name demo # abre o navegador com o shell da máquina ./bin/rc machine web-terminal --name demo --no-open -- sh -c 'echo wt-ok:$((6*7))'
Esperado:
web terminal: http://127.0.0.1:PORTA/?t=<32 hexa>: só loopback, com token; o navegador abre sozinho (--no-opensó imprime);- no navegador, um terminal de verdade (xterm.js): Ctrl+C, setas, editor de tela cheia e o tamanho da janela chegam ao guest, e um F5 mostra de novo a tela recente;
- com o comando, a tela mostra
wt-ok:42, a página avisa que o programa saiu e orcsai com o código dele; /streamsem o token responde 403, e umHostque não seja de loopback também (DNS rebinding).- abas
claude(o comando),shell(um terminal à parte, no 1º clique),eventose, por último, uma aba por porta: um servidor que o programa sobe na VM (qualquer porta) aparece com a app num iframe e responde no127.0.0.1do host.
Para abrir o Claude Code no navegador assim: rc machine web-terminal --name agent -- claude --dangerously-skip-permissions.
8wPlayground público (claude-in-chrome.runner.codes)
Cada visitante ganha uma Fly Machine própria (o Fly não expõe /dev/kvm, então a microVM é a do Fly), com o Claude Code no terminal web e as portas em https://<porta>-<id>.runner.codes/. Teto de 3 máquinas; parada há 30 min desliga, sem uso há 24 h é apagada.
scripts/playground-cloudflare.sh # túnel claude-in-chrome, ingress e CNAMEs (idempotente) scripts/playground-fly.sh setup # apps runner-codes-playground e runner-codes-visitors, rede própria, secrets scripts/playground-image.sh all # imagens visitor e front pelo Nix no builder do Fly scripts/playground-fly.sh deploy # publica o front scripts/playground-token-check.sh # o token do front só alcança o app de visitantes curl -s https://claude-in-chrome.runner.codes/ | grep -c FingerprintJS.load
Esperado:
- a página abre, calcula o visitorId do navegador e leva ao terminal
/v/<id>/?t=<token>com o Claude Code; /api/sessioncom visitante inválido responde 400; com 3 máquinas em uso, 503 "playground is full";https://3000-0000000000.runner.codes/(máquina que não existe) responde 502, edocs.runner.codescontinua 200;scripts/playground-token-check.sh: 200 no app de visitantes, 4xx no site e no próprio front;- dentro da máquina do visitante, abrir conexão para a rede privada do Fly (
_api.internal) falha, e DNS e internet funcionam (scripts/fly-firewall-probe.shmede o mesmo firewall numa máquina descartável).
8xRuntime de Kubernetes (k3s e microk8s)
Cada pod com runtimeClassName: rc vira uma microVM da libkrun, com kernel próprio: o containerd chama o containerd-shim-rc-v2, o container pause liga a VM e os containers da app rodam dentro dela. A VM assume o IP que o CNI deu ao pod (TAP no netns do pod com tc mirred). Precisa de nó Linux com /dev/kvm; os de teste são droplets da DigitalOcean (task k8s:kvm-probe mede).
K8S_NODE=k3s task k8s:node -- create && K8S_NODE=k3s task k8s:node -- provision # idem com K8S_NODE=microk8s # no nó: curl -sfL https://get.k3s.io | sh - ou snap install microk8s --classic task k8s:e2e -- k3s # leva a árvore, compila, roda deploy/k3s/install.sh e deploy/k8s/e2e.sh k3s task k8s:e2e -- microk8s # o mesmo com deploy/microk8s/install.sh task k8s:defects # planta 20 defeitos no código do runtime e confere que os testes pegam
Esperado (19 checagens do deploy/k8s/e2e.sh, falhas: 0 nos dois):
rc-hellofica Ready e o log mostraRC_K8S_OK, o kernel da VM (6.12.91, não o do nó) e o IP do pod naeth0;kubectl execsem TTY, com TTY e com stdin; o código de saída do exec (7) e do container (3) voltam certos;- DNS do cluster, pod rc ↔ pod runc nos dois sentidos e Service ClusterIP até o pod rc;
- dois containers no mesmo pod: mesma VM (mesmo
boot_id) e mesmo emptyDir; - o resumo de estatísticas do kubelet responde (o uso da VM vai para o primeiro container);
- depois do delete, nenhum diretório em
/var/lib/rc-shim/podse nenhum processorc vmmsobra.
Limites declarados: port-forward, hostNetwork, hostIPC e propagação de montagem não atravessam a VM; os volumes são copiados para o share do pod na criação do container (o que o kubelet trocar depois, como token de serviceaccount, não chega); limites de CPU/memória valem para a VM inteira (anotações runner.codes/cpus e runner.codes/memory-mib do pod); o vmm do pod roda sem Landlock/seccomp.
9GPU
Só se a imagem llama estiver construída (o rc doctor mostra ok image llama).
./bin/rc run --stack llama --gpu --code 'vulkaninfo --summary | grep deviceName' ./bin/rc run --stack llama --gpu --timeout 120s --code 'llama-bench -m /opt/models/qwen2.5-0.5b-q4_k_m.gguf -p 128 -n 32 -ngl 99 -dev Vulkan0'
Esperado:
deviceName = Virtio-GPU Venus (Apple M1 Pro), e também uma linha dollvmpipe(Vulkan em software);- a tabela do
llama-benchcomVulkan0,pp128na faixa de 900–1.365 t/s etg32de ~57 t/s.
Detalhes em GPU.
10API HTTP
Num terminal:
./bin/rc serve --pool-size 0
Em outro:
curl -s -X POST localhost:8080/api/v1/machines/teste/exec -H 'content-type: application/json' -d '{"command":["python3","-c","print(6*7)"]}'
curl -s localhost:8080/api/v1/machines | head -c 300; echoEsperado:
{"exitCode":0,"stdout":"42\n","stderr":"",...};- a lista com
"name":"teste"e"state":"running".
Os corpos alternativos e o stream por linha, no mesmo servidor:
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8080/api/v1/machines -H 'content-type: application/json' -d '{"name":"t2","image":"alpine","allowedHost":["a.com"]}'
curl -s -X POST localhost:8080/api/v1/machines -H 'content-type: application/json' -d '{"name":"t3","image":"alpine","allowedHosts":["api.example.com"],"memoryMb":512}'
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8080/api/v1/machines -H 'content-type: application/json' -d '{"name":"t4","image":"alpine","nestedVirt":true}'
curl -sN -X POST localhost:8080/api/v1/machines/teste/exec/stream -H 'content-type: application/json' -d '{"command":["sh","-c","echo a; echo b"]}'
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8080/api/v1/machines/teste/syncEsperado:
400: campo desconhecido (allowedHost, com erro de digitação) é recusado, e não ignorado com a saída aberta;"allow_hosts":["api.example.com"],"enabled":truee"memory_mib":512: os nomes alternativos valem;400nonestedVirt(só passa comrc serve --allow-nested-virt);event: stdout/data: a, depoisdata: b: uma linha por evento, texto cru, e oevent: exitno fim;204nosync(máquina sem volume staged: nada a fazer).
Ctrl+C encerra o serve. A máquina continua ligada, porque é destacada.
10bSDKs Go e Python
Com o serve ainda no ar:
go run ./examples/sdk-go http://127.0.0.1:8080 PYTHONPATH=sdk/python python3 examples/sdk-python/main.py http://127.0.0.1:8080
Esperado, nos dois:
job: 42(job efêmero);machine: sdk-... runningeexec: ola do sdk go(oupython): máquina criada, arquivo gravado e executado;error: NOT_FOUND: a recusa da API chega como erro tipado, com o código estável;- nenhuma máquina
sdk-*sobra em./bin/rc machine ls.
10cSDK Go num projeto de fora (go get)
O módulo Go se chama runner.codes, e https://runner.codes responde ao go get com a meta go-import apontando para o GitHub. Num diretório fora do repositório, com o serve ainda no ar:
curl -s 'https://runner.codes/pkg/client?go-get=1' mkdir /tmp/sdkcheck && cp examples/sdk-go/main.go /tmp/sdkcheck/ && cd /tmp/sdkcheck go mod init sdkcheck go get runner.codes/pkg/client@latest go run . http://127.0.0.1:8080
Esperado:
- o
curldevolve<meta name="go-import" content="runner.codes git https://github.com/andrebassi/runner-codes">; - o
go getbaixarunner.codes v1.1.0(ou mais nova); - a saída do
go runé a mesma do 10b:job: 42,exec: ola do sdk goeerror: NOT_FOUND.
O mesmo para o Python, num venv limpo, instalando pelo GitHub:
python3 -m venv /tmp/sdkcheck/venv /tmp/sdkcheck/venv/bin/pip install "git+https://github.com/andrebassi/runner-codes@v1.2.0#subdirectory=sdk/python" /tmp/sdkcheck/venv/bin/python -I <repositório>/examples/sdk-python/main.py http://127.0.0.1:8080
Esperado: o pip instala runner-code 1.1.1, e a saída é a mesma do 10b (job: 42, exec: ola do sdk python, error: NOT_FOUND). O -I garante que o pacote vem do venv, não do repositório.
10dReceitas dos SDKs
As sete receitas da página de SDKs, em Go e em Python, com o serve ainda no ar:
go run ./examples/sdk-go-recipes http://127.0.0.1:8080 PYTHONPATH=sdk/python python3 examples/sdk-python-recipes/main.py http://127.0.0.1:8080
Esperado, nos dois (o tempo varia):
untrusted: stdout=45 stderr=aviso tempo=58ms files: 42 timeout: exception=Timeout compile: exception=Compilation failed stderr_tem_erro=true machine: sdk-go pypi=200 example=bloqueado branch: feito antes do branch errors: 404 NOT_FOUND
Na linha machine, sdk-python no lugar de sdk-go no segundo programa. Nenhuma máquina recipe-* sobra em ./bin/rc machine ls.
10eDesktop com Claude Code pelo SDK Go
Com o serve ainda no ar, a máquina do examples/desktop-claude/runner.code (XFCE, Firefox, Claude Code), criada e conferida pelo SDK Go:
go run ./examples/desktop-claude -server http://127.0.0.1:8080 -name desk -display-port 6081 open http://127.0.0.1:6081
Esperado, na saída:
claude 2.1.x (Claude Code)enavegador padrão /usr/lib/firefox-esr/firefox-esr;atalhos Arquivos.desktop Claude.desktop Navegador.desktop Terminal.desktop;tela responde ... current 1200 x 700: depois de 200 movimentos de mouse, a tela segue de pé e no tamanho pedido;terminal maximizado _NET_WM_STATE_MAXIMIZED_HORZ;sessão de volta em 1200x700: o programa encerra a sessão XFCE (como um logout) e ela volta sozinha.
No navegador: fundo preto, os quatro atalhos, e a tela no tamanho da janela.
10fTarefas ao Claude Code da máquina
Precisa do Claude logado na máquina: o token entra pela credencial do runner.code (o guest só vê um placeholder), a partir do ambiente de quem deu o start. Com o serve iniciado com o token no ambiente:
CLAUDE_CODE_OAUTH_TOKEN="$(pass show bassi/anthropic/claude-code-token)" ./bin/rc serve --listen 127.0.0.1:8080 & go run ./examples/desktop-claude -server http://127.0.0.1:8080 -name desk -display-port 6081 go run ./examples/desktop-claude-agent -server http://127.0.0.1:8080 -name desk
Esperado: ✓ /root/agent/headless.txt, ✓ /root/agent/gui.txt e ✓ /root/agent/keys.txt, cada um com feito pelo modo <modo>. São três jeitos de mandar a tarefa: claude -p por exec, digitada na janela com xdotool, e digitada pela API de tela. As telas dos dois últimos ficam em agent-gui.jpg e agent-keys.jpg.
O task runbook só roda este passo com RB_CLAUDE=1 e o token no ambiente, porque ele usa a assinatura do Claude.
11Limpar
./bin/rc machine stop --name teste ./bin/rc machine delete --name teste rm -rf /tmp/rc-up.bin /tmp/rc-down.bin /tmp/rc-demo /tmp/rc-vol
Esperado: stopped teste e deleted teste. ./bin/rc machine ls volta vazio.
extraQuando algo sair diferente
| Sintoma | Onde olhar |
|---|---|
image for X not built | ./bin/rc image build --stack X |
read stream: the guest in this image does not support streaming | imagem de antes do streaming: reconstruir |
INVALID_STATE: machine X is not running | ./bin/rc machine start --name X |
CONFLICT: machine X already exists | outro nome, ou delete antes |
start falha com o fim do console | ./bin/rc machine logs --name X |
GPU: ERROR_OUT_OF_HOST_MEMORY | GPU › Por que Fedora |
| máquina que não para | ./bin/rc machine stop --name X --timeout 0 mata na hora |
Fonte: docs/runbook.md · executável: scripts/runbook.sh
Nada aqui é promessa.
Cada recurso tem teste de unidade, uma prova numa MicroVM real e defeitos plantados de propósito para conferir que os testes reprovam.
Plantei 28 defeitos nos recursos novos (rede, ICMP, staged, sockets, nested, SSE…); os testes pegaram os 28. Rodada de 20/10/2026, macOS 27, M1 Pro.
Rápido o bastante para não usar contêiner.
Boot medido em 19/10/2026 (30 boots) e branch em 20/10/2026, num Mac com Apple Silicon. O relatório completo está na documentação.
15 no catálogo, e um LLM.
Cada uma com imagem, teto de CPU e RAM e aquecimento próprios, num YAML. Máquinas aceitam qualquer imagem OCI (redis:7-alpine, postgres:16-alpine…).
- Branch, pausa e checkpoint de disco pronto
- Rede por host, CIDR e rede nomeada, ping incluso pronto
- Credenciais sem ler, segredos e agente SSH pronto
- Pack portátil, volume staged e bucket S3 pronto
--nested(KVM dentro da VM) só a recusa medida
O --nested exige um Mac M3 ou mais novo, ou Linux com KVM aninhado. Neste M1 ele recusa na hora, com o motivo.
Mac e Linux, sem Docker.
O runner.codes usa o hypervisor que o sistema já tem. Sem Docker, sem máquina na nuvem.
Apple Silicon, pelo Hypervisor.framework. É onde os números desta página foram medidos.
x86_64 e aarch64, pelo KVM (o seu usuário precisa acessar /dev/kvm). O processo de cada MicroVM roda com Landlock e seccomp. O boot em Linux com KVM de verdade ainda não foi medido.
A libkrun não tem backend Hyper-V. O caminho é o WSL2 com virtualização aninhada, onde roda o rc de Linux.
Prontos para rodar, testados numa VM.
Cada exemplo sobe numa VM real, e cada guia tem um run.sh que prova o que ele promete.
Três comandos, tudo pelo Nix.
Sem Homebrew e sem Docker. A primeira instalação no macOS compila a libkrun (cerca de 10 min); no Linux ela vem pronta do nixpkgs e o seu usuário precisa acessar /dev/kvm.
./bin/rc doctor confere se o host está pronto.
git clone https://github.com/andrebassi/runner-codes cd runner-codes task krun:install # libkrun + libkrunfw task build # host + guest ./bin/rc image build --stack python # 8–10 s ./bin/rc run --stack python --code 'print("olá da MicroVM")'
Documentação que um agente lê de uma vez.
Cada comando, flag, rota e erro, numa página só.
runner-code.mdO arquivo da máquina, chave por chave.
network.mdPolítica de saída, portas, volumes, S3, rede nomeada.
credentials.mdCredenciais que a máquina usa sem ler.
pack.mdA máquina num executável.
architecture.mdComo o host, o VMM e o guest conversam.
runbook.mdO passo a passo para testar à mão.
languages.yamlO catálogo de linguagens, com imagem e limites.