# Sandboxes

> [!NOTE]
> Os sandboxes são um recurso **experimental**. A API, os padrões e o comportamento podem mudar sem aviso prévio. Os sandboxes compartilhados são destinados a cargas de trabalho dentro do mesmo limite de confiança; seu isolamento não garante proteção contra todos os ataques entre sandboxes. Use sandboxes dedicados para cargas de trabalho que não confiam umas nas outras e evite disponibilizar credenciais de longa duração ou com escopo amplo para as cargas de trabalho dos sandboxes.

Um sandbox é uma máquina em nuvem isolada que você pode iniciar em segundos, executar comandos com saída transmitida ao vivo e mover arquivos para dentro e para fora — tudo a partir do Python ou da CLI. Os sandboxes são construídos sobre os [Jobs](./jobs): por baixo, um sandbox é apenas um Job executando um servidor pequeno que expõe a execução de comandos e a transferência de arquivos via HTTP.

Eles são uma boa opção sempre que você precisar executar código em outro local que não seja sua própria máquina:

- **Executando código não confiável ou gerado por IA** — permita que um agente execute código arbitrário sem conceder a ele acesso ao seu sistema de arquivos. Use um sandbox *dedicado* para isso: ele é uma VM própria.
- **Compilações e experimentos reproduzíveis** — execute em uma imagem limpa e bem definida, em CPU ou GPU.
- **Distribuir o trabalho** — inicie centenas de ambientes paralelos (rollouts de RL, avaliação, execução em lote de ferramentas) de forma econômica.

Qualquer imagem Docker com `/bin/sh` funciona — não é necessário ter Python, pip ou agente pré-instalados (um pequeno binário de servidor estático é injetado na inicialização).

> [!TIP]
> Curioso sobre como isso funciona por baixo dos panos — o servidor dentro do Job, a autenticação stateless ou como um único Job hospeda muitos sandboxes isolados? Veja o [guia conceitual de Sandboxes](../concepts/sandbox).

## Os dois tipos de sandbox

Existem duas formas de obter um sandbox. Ambas entregam o mesmo objeto [Sandbox](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox) (mesmos `run`, `files`, `connect`, `kill`); elas diferem apenas em como a máquina subjacente é alocada:

|            | [Sandbox.create()](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox.create) — **dedicado**              | [SandboxPool](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.SandboxPool) — **compartilhado / pool**                                                     |
| ---------- | ----------------------------------------------- | --------------------------------------------------------------------------------------- |
| mapeamento    | um Job = **um sandbox** (uma VM inteira)          | um Job = **muitos sandboxes** (uma VM, empacotada)                                           |
| isolamento  | VM completo                                         | uid + [Landlock](https://docs.kernel.org/userspace-api/landlock.html) (confiança do mesmo usuário) |
| início a frio | ~6s por sandbox                                 | ~6s para o primeiro host, depois ~1 ida e volta cada                                         |
| custo       | uma VM por sandbox                              | uma VM por **host**, amortizada entre muitos sandboxes                                    |
| GPU        | ✅                                               | ❌ (somente CPU)                                                                            |
| melhor para   | um único sandbox, cargas de trabalho em GPU, código não confiável | muitos sandboxes de CPU baratos (rollouts de RL, fan-out)                                         |

Regra prática: precisa de uma GPU ou de executar código de fontes não confiáveis entre si → dedicado. Precisa de centenas de sandboxes de CPU baratas → um pool.

## Início rápido

```python
>>> from huggingface_hub import Sandbox

>>> with Sandbox.create() as sbx:                      # ready in ~6s
...     result = sbx.run("python -c 'print(40 + 2)'")  # ~100ms per command
...     print(result.stdout)
42
```

Escolha qualquer imagem e sabor [de hardware](./jobs#select-the-hardware):

```python
>>> sbx = Sandbox.create(image="alpine:3.20")
>>> sbx = Sandbox.create(image="pytorch/pytorch:2.6.0-cuda12.4-cudnn9-devel", flavor="a10g-small")
```

## Executando comandos

[Sandbox.run()](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox.run) executa um comando e aguarda por ele. Passe uma string de shell ou uma lista argv:

```python
>>> sbx.run("pip install -q numpy")                       # string  → runs through /bin/sh -c
>>> sbx.run(["python", "-c", "import numpy; print(numpy.__version__)"])  # list → exec'd directly

# Live output streaming, plus env, cwd, timeout, stdin
>>> sbx.run("make -j4", cwd="/app", env={"CC": "gcc"}, timeout=600, on_stdout=print, on_stderr=print)
```

Por padrão, o modo é inferido a partir do tipo (uma string é executada por meio de `/bin/sh -c`, enquanto uma lista é executada diretamente como argv). Passe `shell=` para tornar isso explícito — útil para evitar o clássico erro em que uma lista de um elemento, como `["echo hi"]`, é executada como um único programa chamado `"echo hi"`:

```python
>>> sbx.run("echo $HOME && ls | wc -l", shell=True)   # force the shell (pipes, globs, $VARS)
>>> sbx.run(["git", "commit", "-m", msg], shell=False)  # force argv (no quoting surprises)
```

`shell=True` requer uma string e `shell=False` requer uma lista; passar o tipo errado levanta um `ValueError`.

Um comando que termina com um código de saída diferente de zero levanta `SandboxCommandError` (com `stdout`, `stderr` e `exit_code` anexados). Passe `check=False` para receber o [SandboxCommandResult](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.SandboxCommandResult) de volta em vez de levantar a exceção:

```python
>>> result = sbx.run("test -f /tmp/missing", check=False)
>>> result.exit_code
1
```

### Processos em segundo plano

Pass `background=True` para iniciar um processo de longa duração (um servidor, um watcher, uma execução de treinamento) sem esperar por ele. `run` retorna um [SandboxProcess](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.SandboxProcess) imediatamente em vez de um [SandboxCommandResult](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.SandboxCommandResult):

```python
>>> proc = sbx.run("python -m http.server 8000", background=True)
>>> proc
SandboxProcess(pid=1234, cmd='python -m http.server 8000', tag=None, started_at_ms=1700000000000, running=True, exit_code=None)
```

Liste os processos de um sandbox e pare um deles quando terminar. Os processos concluídos permanecem listados (com `running=False` e seu `exit_code`) até que o sandbox seja excluído, permitindo verificar se um processo ainda está ativo ou já foi encerrado:

```python
>>> sbx.processes()
[SandboxProcess(pid=1234, cmd='python -m http.server 8000', tag=None, started_at_ms=1700000000000, running=True, exit_code=None)]
>>> proc.kill()
```

As opções de streaming/apenas espera (`timeout`, `stdin`, `on_stdout`, `on_stderr`, `check`) não se aplicam no modo em segundo plano — apenas `env`, `cwd` e `shell` são respeitados.

## Arquivos

```python
>>> sbx.files.write("/app/script.py", "print('hi')")     # str | bytes | file-like
>>> sbx.files.read_text("/app/script.py")
"print('hi')"
>>> sbx.files.upload("local_data.csv", "/data/data.csv")  # local -> sandbox
>>> sbx.files.download("/data/results.bin", "results.bin")  # sandbox -> local
>>> sbx.files.list("/data")
[FileEntry(name='data.csv', path='/data/data.csv', type='file', size=5324, ...)]
```

Outros auxiliares: `stat`, `exists`, `mkdir`, `delete`.

## Acessando um servidor dentro de um sandbox

Inicie um servidor no sandbox (em segundo plano) e, em seguida, acesse-o de fora com [Sandbox.proxy_url_for()](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox.proxy_url_for) — a requisição é encaminhada pelo servidor de sandbox do job para o seu servidor interno, de modo que não há necessidade de expor uma porta pública adicional. Funciona para HTTP simples, Server-Sent Events e WebSocket. Combine a URL com `Sandbox.proxy_headers` para autenticação (seu cliente WebSocket/HTTP deve enviá-los):

```python
>>> import httpx2
>>> with Sandbox.create() as sbx:
...     sbx.files.write("app.py", "...")  # a server exposing e.g. /hello and /ws
...     sbx.run("uvicorn app:app --host 127.0.0.1 --port 8000", background=True)
...     # plain HTTP
...     r = httpx2.get(sbx.proxy_url_for(8000, "/hello"), headers=sbx.proxy_headers)
...     # WebSocket: ask for a wss:// URL
...     ws_url = sbx.proxy_url_for(8000, "/ws", scheme="wss://")
```

Ao usar `proxy_headers`, desative os redirecionamentos no seu cliente HTTP (`follow_redirects=False` com httpx, ou `allow_redirects=False` com requests). Um aplicativo pode redirecionar para outra origem, e esses cabeçalhos de autenticação personalizados não devem ser encaminhados para lá.

Sandeboxes agrupados exigem um servidor que retorne tokens de capacidade por sandbox. Tokens ausentes são um erro; atualize e recicle o host em vez de usar seu token de gerenciamento.

Como o servidor interno deve escutar depende do tipo de sandbox:

- **Dedicado** ([Sandbox.create()](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox.create)): vincule uma porta TCP normal em `127.0.0.1:<port>`.
- **Pool / compartilhado** ([SandboxPool](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.SandboxPool)): sandboxes em pool não podem vincular uma porta TCP (Landlock), então escute em um **socket unix** em `$SBX_PROXY_DIR/<port>.sock` (essa variável de ambiente é definida em cada sandbox), por exemplo, `uvicorn app:app --uds $SBX_PROXY_DIR/8000.sock`. O lado do cliente (`proxy_url_for` / `proxy_headers`) é idêntico em qualquer caso.

## Ciclo de vida

Um sandbox sobrevive ao processo que o criou — você pode criá-lo agora e se reconectar depois, de qualquer máquina que possua o mesmo token do HF, sem precisar copiar nenhum estado:

```python
>>> sbx = Sandbox.create()
>>> sbx.id
'687f911eaea852de79c4a50a'

# Later, from anywhere:
>>> sbx = Sandbox.connect("687f911eaea852de79c4a50a")
>>> sbx.kill()           # terminate now
```

- `idle_timeout` (padrão 10 minutos) é o verdadeiro guardião: ele desliga o sandbox quando nenhuma chamada de API é feita e nenhum processo está em execução, para que sandboxes abandonados parem de gerar cobrança. Defina-o no momento da criação (`Sandbox.create(idle_timeout="30m")`) ou passe `None` para desativar.
- O trabalho também possui uma vida útil máxima fixa de 24 horas como limite rígido (não configurável).
- `forward_hf_token=True` expõe explicitamente seu token do HF para o código em execução no sandbox (como `HF_TOKEN`). Mesmo com `forward_hf_token=False`, não trate o sandbox como uma fronteira rígida para suas credenciais — veja o aviso no topo desta página.

## Muitos sandboxes ao mesmo tempo: SandboxPool

Quando você precisa de muitos sandboxes (rollouts de RL paralelos, avaliação em leque, execução em lote de ferramentas), usar um Job por sandbox é desperdício: cada um paga o custo de um cold start completo de VM e ocupa uma máquina inteira para uma carga de trabalho que precisa de poucos MB de RAM. O [SandboxPool](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.SandboxPool) agrupa muitos sandboxes leves em alguns Jobs de host compartilhados — uma única VM cobrada atende a dezenas de sandboxes, de modo que o custo por sandbox diminui nesse fator e o cold start por sandbox é de aproximadamente um round-trip de rede.

```python
>>> from huggingface_hub import SandboxPool

>>> with SandboxPool(image="python:3.12", flavor="cpu-basic", warm_up=2) as pool:
...     boxes = [pool.create() for _ in range(100)]   # packed across the 2 warm host VMs
...     print(boxes[0].run("echo hi").stdout)          # each box is a normal Sandbox
hi
```

Cada `create()` retorna um [Sandbox](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox) completo; chame-o repetidamente para distribuir as tarefas. O pool inicia Jobs no host conforme necessário, empacota `sandboxes_per_host` sandboxes por host e encerra tudo no `close()` (ou quando um host fica ocioso, como uma medida de segurança para faturamento). O padrão típico de distribuição:

```python
>>> from concurrent.futures import ThreadPoolExecutor
>>> with SandboxPool(image="python:3.12", warm_up=4) as pool:
...     boxes = [pool.create() for _ in tasks]
...     with ThreadPoolExecutor(32) as ex:
...         outputs = list(ex.map(lambda b, t: b.run(t.cmd).stdout, boxes, tasks))
```

Env e `idle_timeout` são por sandbox (pertencem a `create()`, não ao pool), então os sandboxes em um pool podem ter ambientes diferentes:

```python
>>> sbx = pool.create(env={"SEED": "42"}, idle_timeout="5m", forward_hf_token=True)
```

Um sandbox agrupado é um [Sandbox](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox) completo, mas alguns inputs são fixados pelo host em vez de serem específicos de cada sandbox — portanto, [SandboxPool.create()](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.SandboxPool.create) aceita um conjunto menor de argumentos do que [Sandbox.create()](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox.create):

| Entrada              | `Sandbox.create` (dedicado) | `SandboxPool.create` (com pool)            |
| ------------------ | ---------------------------- | ---------------------------------------- |
| `image`, `flavor`  | por sandbox                  | fixado pelo pool (definido uma vez no host) |
| `volumes`          | por sandbox                  | **indisponível** (montado na inicialização do host) |
| `env`              | por sandbox                  | por sandbox                              |
| `idle_timeout`     | por sandbox                  | por sandbox                              |
| `forward_hf_token` | por sandbox                  | por sandbox                              |

Os sandboxes agrupados compartilham um host job de longa duração, portanto, não há um canal de Job-secrets criptografados como os sandboxes dedicados recebem. No entanto, o `env` de um sandbox agrupado é entregue ao servidor host na criação do sandbox (e não no início do job), portanto, nunca é armazenado nos metadados de nenhum job — passe os segredos pretendidos como `env` simples.

### Crescimento sob demanda e pré-aquecimento

`pool.create()` cria um sandbox por vez, reutilizando um host que ainda tem capacidade livre antes de iniciar um novo — assim, você pode criar sandboxes conforme o trabalho chega e eles se acomodam em hosts já aquecidos:

```python
>>> pool = SandboxPool(image="python:3.12", flavor="cpu-basic")
>>> sbx = pool.create()    # boots the first host (~6s)
>>> sbx = pool.create()    # packs onto the same warm host (~one round-trip, no new VM)
```

Para evitar o início frio do host nas primeiras chamadas, provisione os hosts com antecedência usando `warm_up=N` (iniciado no primeiro `create()`) ou chamando `pool.warm(N)` previamente.

### Reutilizando pools entre processos e máquinas

Hosts quentes são descobertos por meio de rótulos de job, de modo que a reutilização funciona entre processos: um `SandboxPool` totalmente novo com o mesmo `image`/`flavor`/`name` se conecta aos hosts que uma execução anterior deixou em execução, em vez de iniciar os próprios. Passe um `name=` para evitar que pools distintos compartilhem hosts.

Para reanexar de outra máquina sem estado local, reconecte pelo id do pool com [SandboxPool.connect()](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.SandboxPool.connect) — ele encontra um host em execução, reconstrói a configuração do pool (imagem, flavor, densidade de empacotamento) a partir do job desse host e fica pronto para `create()` mais:

```python
>>> pool = SandboxPool.connect("pool-ae9f7efe0bc7")   # from anywhere, no config needed
>>> sbx = pool.create()
```

Um pool com `connect()` não é proprietário dos hosts compartilhados (outros clientes podem estar usando-os), portanto — assim como [Sandbox.connect()](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox.connect) — sair do bloco `with` (ou chamar `close()`) apenas libera os clientes HTTP locais e deixa os hosts em execução. Encerre os hosts de um pool explicitamente com `pool delete` / `hf sandbox pool delete <id>`.

> [!NOTE]
> Os sandboxes dentro de um host são separados por uids distintos e conjuntos de regras Landlock específicos para cada sandbox. Sandboxes compartilhados destinam-se a cargas de trabalho paralelas do *próprio usuário*, e o isolamento contra todos os ataques entre sandboxes não é garantido. Para código mutuamente não confiável, ou para GPU, use [Sandbox.create()](/docs/huggingface_hub/v2.4.0/pt/package_reference/sandbox#huggingface_hub.Sandbox.create) (uma VM separada por sandbox). Os trade-offs são detalhados no guia conceitual: [como o isolamento funciona](../concepts/sandbox#isolation-in-a-pool-uid--landlock) e — mais importante se você estiver escolhendo entre os dois modos — a lista completa de [limitações conhecidas](../concepts/sandbox#known-limitations).

## Da CLI

O comando `hf sandbox` espelha a API em Python. Um sandbox dedicado:

```bash
>>> hf sandbox create
✓ Sandbox ready id=687f911eaea852de79c4a50a image=python:3.12 elapsed=6.0s

# Attach labels to the underlying Job
>>> hf sandbox create --label controller-run=run-42 --label team=data-infra

>>> hf sandbox exec 687f911eaea852de79c4a50a -- python -c "print('hi')"
hi

>>> hf sandbox cp data.csv 687f911eaea852de79c4a50a:/data/data.csv
>>> hf sandbox kill 687f911eaea852de79c4a50a
```

`hf sandbox exec` transmite a saída em tempo real e encerra com o código de saída do comando, o que permite sua composição em scripts:

```bash
hf sandbox exec $ID -- pytest && echo "tests passed"
```

Inicie um processo de longa duração em segundo plano com `hf sandbox spawn` (imprime seu pid), depois liste ou pare processos. A listagem mostra o status de cada processo (`running` ou `exited (<code>)`):

```bash
>>> hf sandbox spawn $ID -- python -m http.server 8000
✓ Process started sandbox=687f... pid=1234

>>> hf sandbox process ls $ID
pid   status   cmd
1234  running  python -m http.server 8000

>>> hf sandbox process kill $ID 1234
```

Para muitos sandboxes compartilhados de baixo custo, aqueça um pool uma vez e, em seguida, crie instâncias nele sob demanda:

```bash
# Warm a pool -> prints a pool id (billing starts: a host VM is now running)
>>> hf sandbox pool create python:3.12 --flavor cpu-basic
✓ Pool created id=pool-ae9f7efe0bc7 image=python:3.12 flavor=cpu-basic host=687f... elapsed=5.7s

# Each create packs onto a host with room (found by the pool id, from any machine);
# only when every host is full does it boot a duplicate. Env is per-sandbox (pooled
# sandboxes share a host, so there's no encrypted-secrets channel — use --env).
>>> hf sandbox create --pool pool-ae9f7efe0bc7 --env LOG_LEVEL=debug
>>> hf sandbox create --pool pool-ae9f7efe0bc7

>>> hf sandbox pool ls
>>> hf sandbox pool delete pool-ae9f7efe0bc7    # terminate the pool's hosts (and their sandboxes)
```

`hf sandbox create --pool` produces a shared sandbox; its id looks like `<host_job_id>.<local_id>` and works everywhere a dedicated id does (`exec`, `cp`, `kill`). A pool has no local state — it is just its running host VMs, found by the pool id — so it works from any machine and stops existing once all its hosts are gone (killed or idle-timed-out).

