Hub Python Library documentation

Sandboxes

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v2.4.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Sandboxes

Los sandbox son una función experimental. La API, los valores predeterminados y el comportamiento pueden cambiar sin previo aviso. Los sandbox compartidos están destinados a cargas de trabajo dentro del mismo límite de confianza; su aislamiento no garantiza protección contra todos los ataques entre sandbox. Utilice sandbox dedicados para cargas de trabajo que no se confían entre sí y evite hacer disponibles credenciales de larga duración o de amplio alcance para las cargas de trabajo del sandbox.

Un sandbox es una máquina en la nube aislada que puedes iniciar en segundos, en la que puedes ejecutar comandos con salida en tiempo real y mover archivos hacia dentro y hacia fuera, todo desde Python o la CLI. Los sandboxes se construyen sobre Jobs: por debajo, un sandbox es simplemente un Job que ejecuta un servidor pequeño que expone la ejecución de comandos y la transferencia de archivos a través de HTTP.

Son una buena opción siempre que necesites ejecutar código en otro lugar que no sea tu propia máquina:

  • Ejecución de código no confiable o generado por IA — permite que un agente ejecute código arbitrario sin darle acceso a tu sistema de archivos. Usa un sandbox dedicado para esto: es una VM propia.
  • Compilaciones y experimentos reproducibles — ejecútelos en una imagen limpia y bien definida, en CPU o GPU.
  • Distribuir el trabajo — lanzar cientos de entornos paralelos (despliegues de RL, evaluación, ejecución de herramientas por lotes) de forma económica.

Cualquier imagen de Docker con /bin/sh funciona: no es necesario tener Python, pip ni un agente preinstalados (se inyecta un pequeño binario de servidor estático al inicio).

¿Te interesa saber cómo funciona por dentro: el servidor dentro del trabajo, la autenticación sin estado o cómo un solo Job aloja muchos sandbox aislados? Consulta la guía conceptual de Sandboxes.

Los dos tipos de sandbox

Hay dos formas de obtener un sandbox. Ambas te entregan el mismo objeto Sandbox (con el mismo run, files, connect, kill); solo difieren en cómo se asigna la máquina subyacente:

Sandbox.create() — dedicadoSandboxPool — compartido / pool
mappingun trabajo = un sandbox (una VM completa)un trabajo = muchos sandbox (una VM, empaquetada)
aislamientoVM completauid + Landlock (confianza del mismo usuario)
arranque en frío~6 s por sandbox~6s para el primer host, luego ~1 ida y vuelta cada uno
costeuna VM por sandboxuna VM por host, amortizada entre muchos sandbox
GPU✅❌ (Solo CPU)
ideal paraun solo sandbox, cargas de trabajo en GPU, código no confiablemuchas sandboxes de CPU económicas (despliegues de RL, fan-out)

Regla general: si necesita una GPU o ejecutar código de confianza mutua → dedicado. Si necesita cientos de sandbox de CPU económicos → un pool.

Introducción

>>> 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

Elige cualquier imagen y variante de hardware:

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

Ejecución de comandos

Sandbox.run() ejecuta un comando y espera a que termine. Pasa una cadena de shell o una lista argv:

>>> 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 defecto, el modo se infiere a partir del tipo (una cadena se ejecuta a través de /bin/sh -c, mientras que una lista se ejecuta directamente como argv). Pasa shell= para hacerlo explícito, lo cual es útil para evitar el clásico error en el que una lista de un solo elemento como ["echo hi"] se ejecuta como un único programa llamado "echo hi":

>>> 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 requiere una cadena y shell=False requiere una lista; pasar el tipo incorrecto genera un ValueError.

Un comando que finaliza con un código de salida no cero lanza SandboxCommandError (con stdout, stderr y exit_code adjuntos). Pasa check=False para obtener el SandboxCommandResult en lugar de lanzar una excepción:

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

Procesos en segundo plano

Pasa background=True para iniciar un proceso de larga duración (un servidor, un watcher, una ejecución de entrenamiento) sin esperar a que termine. run devuelve un SandboxProcess de inmediato en lugar de un SandboxCommandResult:

>>> 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)

Enumera los procesos de un sandbox y detén uno cuando termines. Los procesos finalizados permanecen en la lista (con running=False y su exit_code) hasta que se elimine el sandbox, por lo que puedes saber si un proceso sigue activo o ya ha finalizado:

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

Las opciones de streaming/solo espera (timeout, stdin, on_stdout, on_stderr, check) no se aplican en modo de segundo plano: solo se respetan env, cwd y shell.

Archivos

>>> 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, ...)]

Otros auxiliares: stat, exists, mkdir, delete.

Acceder a un servidor dentro de un sandbox

Inicie un servidor en el sandbox (en segundo plano) y acceda a él desde el exterior con Sandbox.proxy_url_for(): la solicitud se reenvía desde el servidor de sandbox del trabajo a su servidor interno, por lo que no es necesario exponer un puerto público adicional. Funciona con HTTP simple, Server-Sent Events y WebSocket. Combine la URL con Sandbox.proxy_headers para la autenticación (su cliente de WebSocket/HTTP debe enviarlos):

>>> 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://")

Al usar proxy_headers, deshabilita las redirecciones en tu cliente HTTP (follow_redirects=False con httpx, o allow_redirects=False con requests). Una aplicación puede redirigir a otro origen, y estas cabeceras de autenticación personalizadas no deben reenviarse allí.

Los sandbox agrupados requieren un servidor que devuelva tokens de capacidad por sandbox. La ausencia de tokens es un error; actualice y recicle el host en lugar de usar su token de administración.

Cómo debe escuchar el servidor interno depende del tipo de sandbox:

  • Dedicado (Sandbox.create()): asocia un puerto TCP normal en 127.0.0.1:<port>.
  • Pool / compartido (SandboxPool): los sandbox agrupados no pueden vincular un puerto TCP (Landlock), por lo que hay que escuchar en un socket unix en $SBX_PROXY_DIR/<port>.sock (esa variable de entorno se establece en cada sandbox), por ejemplo uvicorn app:app --uds $SBX_PROXY_DIR/8000.sock. El lado del cliente (proxy_url_for / proxy_headers) es idéntico en ambos casos.

Ciclo de vida

Un sandbox sobrevive al proceso que lo creó: puedes crearlo ahora y reconectar más tarde, desde cualquier máquina que tenga el mismo token de HF, sin necesidad de copiar el estado:

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

# Later, from anywhere:
>>> sbx = Sandbox.connect("687f911eaea852de79c4a50a")
>>> sbx.kill()           # terminate now
  • idle_timeout (10 minutos por defecto) es la clave: apaga el sandbox cuando no se realiza ninguna llamada a la API y no hay ningún proceso en ejecución, de modo que los sandboxes abandonados dejan de facturar. Configúralo en el momento de la creación (Sandbox.create(idle_timeout="30m")) o pasa None para desactivarlo.
  • El trabajo también tiene una vida máxima fija de 24 horas como respaldo definitivo (no configurable).
  • forward_hf_token=True expone explícitamente tu token de HF al código que se ejecuta en el sandbox (como HF_TOKEN). Incluso con forward_hf_token=False, no trates el sandbox como una frontera rígida para tus credenciales; consulta la advertencia en la parte superior de esta página.

Varios sandbox a la vez: SandboxPool

Cuando necesitas muchos sandbox (despliegues paralelos de RL, evaluación con fan-out, ejecución por lotes de herramientas), asignar un Job por sandbox es ineficiente: cada uno incurre en un arranque en frío completo de la VM y ocupa una máquina entera para una carga de trabajo que requiere unos pocos MB de RAM. SandboxPool agrupa muchos sandbox ligeros en unos pocos Job de host compartidos: una VM facturada sirve a decenas de sandbox, por lo que el costo por sandbox se reduce en ese factor y el arranque en frío por sandbox es de aproximadamente un viaje de red.

>>> 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() devuelve un Sandbox completo; llámalo repetidamente para distribuir la carga. El pool inicia Jobs en el host según sea necesario, empaqueta sandboxes_per_host sandboxes por host y termina todo en close() (o cuando un host queda inactivo, como medida de seguridad de facturación). El patrón de distribución típico:

>>> 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 y idle_timeout son por sandbox (pertenecen a create(), no al pool), por lo que los sandboxes de un mismo pool pueden tener entornos diferentes:

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

Un sandbox agrupado es un Sandbox completo, pero algunas entradas están fijadas por el host en lugar de ser específicas de cada sandbox; por lo tanto, SandboxPool.create() acepta un conjunto menor de argumentos que Sandbox.create():

EntradaSandbox.create (dedicado)SandboxPool.create (con agrupación)
image, flavorpor sandboxfijado por el pool (se establece una vez en el host)
volumespor sandboxno disponible (montado en el arranque del host)
envpor sandboxpor sandbox
idle_timeoutpor sandboxpor sandbox
forward_hf_tokenpor sandboxpor sandbox

Los sandbox agrupados comparten un trabajo de host de larga duración, por lo que no existe un canal de secretos cifrados de Job como el que reciben los sandbox dedicados. Sin embargo, el env de un sandbox agrupado se entrega al servidor de host en la creación del sandbox (no en el inicio del trabajo), por lo que nunca se almacena en los metadatos de ningún trabajo: pase los secretos previstos como env plano.

Crecimiento bajo demanda y precalentamiento

pool.create() crea un sandbox a la vez, reutilizando un host que aún tenga capacidad libre antes de iniciar uno nuevo: así puedes generar sandboxes a medida que llega el trabajo y estos se agrupan en hosts ya calientes:

>>> 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 el arranque en frío del host en las primeras llamadas, preconfigure los hosts con warm_up=N (se inicia en la primera create()) o llamando a pool.warm(N) de antemano.

Reutilización de pools entre procesos y máquinas

Los hosts calientes se detectan a través de las etiquetas de trabajo, por lo que la reutilización funciona entre procesos: un SandboxPool nuevo con la misma image/flavor/name se conecta a los hosts que una ejecución anterior dejó en ejecución, en lugar de iniciar los suyos propios. Pasa un name= para evitar que pools distintos compartan hosts.

Para volver a conectar desde otra máquina sin estado local, reconecta por id de pool con SandboxPool.connect(): encuentra un host en ejecución, reconstruye la configuración del pool (imagen, flavor, densidad de empaquetado) desde ese trabajo del host y queda listo para create() más:

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

Un pool con connect() no es propietario de los hosts compartidos (otros clientes pueden estar usándolos), por lo que — al igual que Sandbox.connect() — salir de su bloque with (o llamar a close()) solo libera los clientes HTTP locales y deja los hosts en ejecución. Finaliza los hosts de un pool explícitamente con pool delete / hf sandbox pool delete <id>.

Los sandbox dentro de un host se separan mediante uids distintos y conjuntos de reglas Landlock por sandbox. Los sandbox compartidos están destinados a cargas de trabajo paralelas del propio usuario, y no se garantiza el aislamiento frente a todos los ataques entre sandbox. Para código mutuamente no confiable, o para GPU, use Sandbox.create() (una VM separada por sandbox). Los compromisos se detallan en la guía conceptual: cómo funciona el aislamiento y —más importante si está eligiendo entre los dos modos— la lista completa de limitaciones conocidas.

Desde la CLI

El comando hf sandbox replica la API de Python. Un sandbox dedicado:

>>> 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 la salida en vivo y finaliza con el código de salida del comando, por lo que se puede combinar en scripts:

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

Inicie un proceso de larga duración en segundo plano con hf sandbox spawn (muestra su pid) y luego liste o detenga procesos. La lista muestra el estado de cada proceso (running o exited (<code>)):

>>> 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 muchos sandbox compartidos económicos, calienta un pool una vez y luego crea en él bajo demanda:

# 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 crea un sandbox compartido; su id tiene el formato <host_job_id>.<local_id> y funciona en cualquier lugar donde funcione un id dedicado (exec, cp, kill). Un pool no tiene estado local: son simplemente sus VMs host en ejecución, identificadas por el id del pool, por lo que funciona desde cualquier máquina y deja de existir cuando todos sus hosts desaparecen (por kill o por tiempo de inactividad).

Update on GitHub