Skip to main content

Thunder Sandbox for Python

Create short-lived GPU sandboxes, run commands over SSH, and move files with a small, typed Python API.

Thunder Sandbox uses the same account and credentials as the Thunder CLI. It handles sandbox lifecycle, SSH key creation, readiness polling, command execution, uploads, and downloads through one synchronous and asynchronous Python API.

Installation

Thunder Sandbox requires Python 3.10 or newer.

pip install thunder-sandbox

To install the current development branch directly from GitHub:

pip install git+https://github.com/Thunder-Compute/thunder-sandbox.git

Authenticate with the Thunder CLI before using the library:

tnr login

Alternatively, set TNR_API_TOKEN and, when using a non-default API endpoint, TNR_API_URL. API endpoints must use HTTPS.

Quick start

import thunder_sandbox as thunder

sandbox = thunder.Sandbox.create(
    cpu=4,
    memory=32,
    storage=50,
    gpu_type=thunder.GPUType.A6000,
    gpu_count=1,
    timeout=900,
)

try:
    sandbox.wait_until_ready()

    process = sandbox.exec("nvidia-smi")
    stdout = process.stdout.read()
    exit_code = process.wait()
    if exit_code != 0:
        raise RuntimeError(process.stderr.read())
    print(stdout)
finally:
    sandbox.terminate()

Sandboxes are addressed by id. Sandbox.create() generates an Ed25519 key pair and stores it under ~/.thunder/sandbox_keys/<sandbox-id>. The public key is immutable for the lifetime of the sandbox.

A name is an optional label. It must be free of any other live sandbox in the organization, and it is released once a sandbox finishes, so the same label can be reused later. A name never addresses a sandbox:

sandbox = thunder.Sandbox.create(name="training-run", gpu_type=thunder.GPUType.H100)
print(sandbox.id, sandbox.name)

# Claiming a name a live sandbox already holds raises ConflictError.
# Looking one up searches live sandboxes; prefer Sandbox.from_id.
same = thunder.Sandbox.from_name("training-run")

Handling errors

Conditions worth retrying are typed, so they can be caught without matching on message text. Each carries the API's code, the HTTP status, and the server's retry_after hint when one was sent:

try:
    sandbox = thunder.Sandbox.create(gpu_type=thunder.GPUType.H100)
except thunder.CapacityError as exc:
    # No free GPU of that type right now; the request was fine.
    time.sleep(exc.retry_after or 30)
except thunder.RetryableError:
    # Rate limited, or Thunder could not service the request.
    ...

Run commands

Pass command arguments separately to avoid local shell interpretation:

process = sandbox.exec("python3", "-c", "print('hello from Thunder')")
print(process.stdout.read())
exit_code = process.wait()

Commands can set a working directory, environment variables, a timeout, or a pseudo-terminal:

process = sandbox.exec(
    "python3",
    "train.py",
    workdir="/home/ubuntu/project",
    env={"MODEL": "llama", "DEBUG": "1"},
    timeout=600,
)

Transfer files

sandbox.upload("model.py", "/home/ubuntu/model.py")
sandbox.upload("dataset", "/home/ubuntu/dataset", recursive=True)

sandbox.download("/home/ubuntu/results.json", "results.json")
sandbox.download("/home/ubuntu/checkpoints", "checkpoints", recursive=True)

Network policies

Sandboxes have unrestricted outbound access by default. Restriction is always explicit:

# No outbound internet access.
closed = thunder.Sandbox.create(block_network=True)

# Only the specified CIDRs and domains are permitted.
restricted = thunder.Sandbox.create(
    outbound_cidr_allowlist=["203.0.113.0/24"],
    outbound_domain_allowlist=["pypi.org", "files.pythonhosted.org"],
)

CIDR and domain allowlists are independent. Supply both when restricted workloads need both direct IP and DNS-based access.

For policy updates, None leaves that dimension unrestricted, while an empty sequence blocks it. Each call replaces the complete policy rather than merging with the previous allowlists.

Replace the complete outbound policy of a running sandbox with the same options used at creation:

# Permit package downloads while blocking other destinations.
restricted.update_network_policy(
    outbound_domain_allowlist=["pypi.org", "files.pythonhosted.org"],
)

# Block all outbound network access.
restricted.update_network_policy(block_network=True)

# Restore unrestricted outbound access.
restricted.update_network_policy()

update_network_policy() returns after Thunder accepts the desired policy; enforcement on the sandbox's node converges asynchronously. Tightening a policy blocks new connections but does not currently guarantee that already-established connections are terminated.

Environment and lifetime

sandbox = thunder.Sandbox.create(
    env={"EXPERIMENT": "baseline"},
    timeout=3600,
)

timeout is the sandbox lifetime in seconds. Set it to None to create a sandbox without an enforced TTL.

Work with existing sandboxes

with thunder.Client.from_cli() as client:
    for sandbox in client.list_sandboxes():
        print(sandbox.id, sandbox.status.value)

    sandbox = client.get_sandbox("sbx-0123456789abcdef")
    sandbox.wait_until_ready(timeout=300)
    print(" ".join(sandbox.ssh_command))

The private SSH key must still exist locally to execute commands or transfer files against an existing sandbox.

Async API

Every blocking operation has an awaitable _async twin on the same public class. This makes it possible to use one import and pass Client, Sandbox, and Process objects between synchronous and asynchronous application code:

import asyncio
import thunder_sandbox as thunder


async def main() -> None:
    sandbox = await thunder.Sandbox.create_async(
        gpu_type=thunder.GPUType.A6000,
        gpu_count=1,
    )
    try:
        await sandbox.wait_until_ready_async()
        process = await sandbox.exec_async("nvidia-smi")
        exit_code = await process.wait_async()
        if exit_code != 0:
            raise RuntimeError(await process.stderr.read_async())
        print(await process.stdout.read_async())
    finally:
        await sandbox.terminate_async()


asyncio.run(main())

Configuration

Configuration is resolved from the following sources:

  1. Explicit ClientConfig values.
  2. TNR_API_TOKEN and TNR_API_URL environment variables.
  3. Thunder CLI state in ~/.thunder/cli_config.json.
  4. The default Thunder API endpoint.

Set TNR_HOME to use a different directory for CLI state and sandbox SSH keys.

License

Thunder Sandbox is available under the Apache License 2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

thunder_sandbox-0.5.0.tar.gz (30.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

thunder_sandbox-0.5.0-py3-none-any.whl (34.4 kB view details)

Uploaded Python 3

File details

Details for the file thunder_sandbox-0.5.0.tar.gz.

File metadata

  • Download URL: thunder_sandbox-0.5.0.tar.gz
  • Upload date:
  • Size: 30.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for thunder_sandbox-0.5.0.tar.gz
Algorithm Hash digest
SHA256 f5c803c296a1d6cf1da1793c6d1203b2c23354fbbf3bb4fc61743de4672f8772
MD5 9a5e489536e6a393cd652c93566cc3d0
BLAKE2b-256 1ed8e33008283441471aaa21f21bd8d6d218a7b18916055c53d6011a39a46fe5

See more details on using hashes here.

Provenance

The following attestation bundles were made for thunder_sandbox-0.5.0.tar.gz:

Publisher: release.yml on Thunder-Compute/thunder-sandbox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file thunder_sandbox-0.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for thunder_sandbox-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ff2887ff7b8be31cf4ea882e2a94bbf090f4f87c3888a62f01f59b40d7c7eeb4
MD5 946d370e89ef3c1fd46a3f0dbd360f75
BLAKE2b-256 dd0db9d70a289ab2b7b3178f0d0dac4ac71f9775f8b9b9a203f974c1c808d30e

See more details on using hashes here.

Provenance

The following attestation bundles were made for thunder_sandbox-0.5.0-py3-none-any.whl:

Publisher: release.yml on Thunder-Compute/thunder-sandbox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.7.0

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

This release

0.5.0 This release

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page