Complete API reference for the BoxLite Python SDK.
Python: 3.10+ Platforms: macOS (Apple Silicon), Linux (x86_64, ARM64)
- Runtime Management
- Box Handle
- SSH Control
- Network Tunnels
- Command Execution
- Box Types
- Sync API
- Error Types
- Metrics
- Constants
The main runtime for creating and managing boxes.
from boxlite import Boxlite, Options, BoxOptions, ImageRegistry| Method | Signature | Description |
|---|---|---|
default() |
() -> Boxlite |
Create runtime with default settings (~/.boxlite) |
__init__() |
(options: Options) -> Boxlite |
Create runtime with custom options |
| Method | Signature | Description |
|---|---|---|
create() |
async (options: BoxOptions, name: str = None) -> Box |
Create a new box |
get() |
async (box_id: str) -> Optional[Box] |
Reattach to an existing box by ID |
get_info() |
async (box_id: str) -> Optional[BoxInfo] |
Get current box metadata by ID or name |
list_info() |
async () -> List[BoxInfo] |
List all boxes |
metrics() |
async () -> RuntimeMetrics |
Get runtime-wide metrics |
images |
property -> ImageHandle |
The images this runtime can boot from; see boxlite.ImageHandle |
registries |
property -> RegistryHandle |
The server's registry logins; raises UnsupportedError on a local runtime. See boxlite.RegistryHandle |
# Default runtime
runtime = Boxlite.default()
# Custom runtime
runtime = Boxlite(Options(home_dir="/custom/path"))
# Registry host config with auth
runtime = Boxlite(Options(
image_registries=[
ImageRegistry(
host="registry.example.com",
username="user",
password="password",
)
]
))
# Create a box
box = await runtime.create(BoxOptions(image="alpine:latest"))
# List all boxes
for info in await runtime.list_info():
print(f"{info.id}: {info.state.status}")The images a runtime can boot from: the local cache on an embedded runtime,
the server catalog on a REST runtime. Registry credentials are accessed through
runtime.images.registries.
| Operation | Embedded runtime | REST runtime |
|---|---|---|
pull |
Pulls into the cache | Unsupported; creating a box pulls |
list |
One row per cached reference | Catalog alias and digest references |
get |
Cached versions under the name | Catalog versions, aliases and history |
remove |
Forgets the name; layers stay | Removes the entry; refused while a box can boot from it |
usage |
Unsupported | Image count, allowance and known bytes |
create_alias() |
Unsupported | Creates a new alias for a ready digest; existing names conflict |
delete_alias() |
Unsupported | Removes only the named alias; absent names return NotFound |
get and remove take an image name, without a tag or digest, such as
quay.io/acme/app. Alias names use OCI tag syntax; creation takes the
sha256: digest of a ready version recorded for that image.
A registry tag belongs to the upstream registry. A BoxLite alias belongs to the runtime catalog and selects a recorded digest independently. A successful pull seeds an alias from the registry tag; it never overwrites an existing binding. Creating an alias never pushes a registry tag or pulls image bytes.
Alias creation refuses every existing name, even for the same digest, with
AlreadyExists. Delete the alias explicitly before creating it again. Deletion
removes only the binding: versions and existing boxes keep their digests.
Creation history remains available; deletion is recorded by the server audit log.
Image details and versions expose aliases. Their history contains
ImageAliasEvent records with alias, optional from_digest, to_digest,
optional actor and recorded_at. A local cache mirrors its cached registry
tags in aliases, leaves history empty and refuses alias writes.
detail = await runtime.images.get("quay.io/acme/app")
digest = detail.versions[0].digest
await runtime.images.create_alias(detail.name, "verified", digest)
await runtime.images.delete_alias(detail.name, "verified")
logins = await runtime.images.registries.list()The logins a REST server presents when it pulls a private image for a box.
runtime.images.registries raises UnsupportedError on a local runtime, which pulls
with the logins in Options(image_registries=...) instead.
| Method | Signature | Description |
|---|---|---|
list() |
async () -> List[RegistryCredential] |
Every login the organization holds, oldest first |
create() |
async (*, registry_host: str, username: str, password: str, repository_prefix: str | None = None) -> RegistryCredential |
Add a login; AlreadyExistsError while one is held for the same registry and prefix |
remove() |
async (id: str) -> None |
Remove a login; InvalidStateError, naming the boxes, while a box still pulls through it; NotFoundError for an unknown id |
create() takes keyword arguments only. repository_prefix is whole path
segments ending in /, such as "acme/"; leave it out for the whole
registry. The password goes up once: RegistryCredential has id,
registry_host, repository_prefix, username, created_by and
created_at, and no password. remove() raises InvalidArgumentError for an
id that is not a UUID, before any request.
import os
from boxlite import Boxlite, BoxliteRestOptions, BoxOptions
runtime = Boxlite.rest(BoxliteRestOptions.from_env())
login = await runtime.images.registries.create(
registry_host="ghcr.io",
repository_prefix="acme/",
username="acme-bot",
password=os.environ["GHCR_TOKEN"],
)
box = await runtime.create(BoxOptions(image="ghcr.io/acme/private-app:1"))
for login in await runtime.images.registries.list():
print(login.registry_host, login.repository_prefix, login.username)Runtime configuration options.
| Field | Type | Default | Description |
|---|---|---|---|
home_dir |
str |
~/.boxlite |
Base directory for runtime data |
image_registries |
List[ImageRegistry] |
[] |
Registry transport, TLS, search, and auth configuration |
ImageRegistry(
host="registry.local:5000",
transport="http",
search=True,
)
ImageRegistry(
host="registry.example.com",
skip_verify=True,
username="user",
password="password",
)
ImageRegistry(
host="ghcr.io",
bearer_token="token",
)| Field | Type | Default | Description |
|---|---|---|---|
host |
str |
Required | Registry host, optionally with port. Do not include http:// or https:// |
transport |
str |
"https" |
"https" or "http" |
skip_verify |
bool |
False |
Disable TLS certificate and hostname verification for HTTPS registries |
search |
bool |
False |
Include this host when resolving unqualified image references |
username / password |
str | None |
None |
Basic auth credentials. Provide both together |
bearer_token |
str | None |
None |
Bearer token auth |
Configuration options for creating a box.
| Field | Type | Default | Description |
|---|---|---|---|
image |
str |
Required | OCI image URI (e.g., "python:slim", "alpine:latest") |
cpus |
int |
1 |
Number of CPU cores (1–16, the guest kernel's limit) |
memory_mib |
int |
512 |
Memory limit in MiB (128-65536) |
disk_size_gb |
int | None |
None |
Container disk size in GB, never smaller than the image (None = image size) |
working_dir |
str |
"/root" |
Working directory inside container |
env |
List[Tuple[str, str]] |
[] |
Environment variables as (key, value) pairs |
volumes |
List[Tuple | Dict] |
[] |
Volume mounts; tuple = host bind, dict = managed_volume or host_path |
network |
NetworkSpec | None |
None |
Structured network configuration. Omit for default enabled networking. |
ports |
List[Tuple | Dict] |
[] |
Local TCP forwarding; omit host_port in a dict for automatic allocation |
secrets |
List[Secret] |
[] |
Outbound HTTPS secret substitution rules |
advanced |
AdvancedBoxOptions | None |
None |
Expert-only options, including capabilities.add and capabilities.drop |
auto_delete |
int | None |
None |
0 keeps the box after stop; above 0, a REST runtime deletes it that many seconds after stop and a local runtime at stop. None keeps the runtime's default (auto_remove locally) |
auto_remove |
bool |
True |
Deprecated: use auto_delete. Auto cleanup when stopped |
detach |
bool |
False |
Survive parent process exit |
Capability policy is intentionally nested with the other expert-only options:
from boxlite import AdvancedBoxOptions, BoxOptions, ContainerCapabilities
options = BoxOptions(
image="alpine:latest",
advanced=AdvancedBoxOptions(
capabilities=ContainerCapabilities(
add=["NET_BIND_SERVICE"],
drop=["NET_RAW"],
)
),
)from boxlite import NetworkSpec
network = NetworkSpec(
mode="enabled",
allow_net=["api.openai.com"],
)| Field | Type | Default | Description |
|---|---|---|---|
mode |
str |
Required | "enabled" or "disabled" |
allow_net |
List[str] |
[] |
Outbound allowlist used only when mode="enabled" |
A host matched by a configured Secret is additionally reachable on port 443
without a rule of its own, so allow_net is not the only egress gate. The
connection is dialed by name, and under a non-empty allow_net an answer in a
private, loopback or CGNAT range is refused unless an IP or CIDR rule covers it.
mode="disabled" removes the guest network interface entirely. No secret makes
a host reachable in that mode — there is no network to reach it over.
A mount has exactly one origin. A tuple is always a host bind:
volumes=[
("/host/config", "/etc/app/config", True), # Read-only
("/host/data", "/mnt/data", False), # Read-write
]A dict takes managed_volume — the volume's server-assigned id or its
name, the server resolves either — or host_path, never both:
volumes=[
{"managed_volume": "my-data", "guest_path": "/data"},
{"managed_volume": "vol_01K2EXAMPLE", "guest_path": "/cache"},
{"host_path": "/host/config", "guest_path": "/etc/app/config"},
]Managed volumes require a REST runtime; host binds are local-runtime only.
read_only is rejected on a managed mount - only host binds may be read-only.
ports=[
(8080, 80, "tcp"), # HTTP
(5432, 5432, "tcp"), # PostgreSQL
{"guest_port": 3000}, # OS-selected host port
]Port publication is local-only and TCP-only. For portable local/remote access,
use box.network.tunnel(("127.0.0.1", port)); each returned tunnel is one-shot.
from boxlite import Secret
secrets=[
Secret(
name="openai",
value="sk-...",
hosts=["api.openai.com"],
# placeholder defaults to "<BOXLITE_SECRET:openai>"
)
]Each host a secret matches becomes reachable on port 443 under a non-empty
allow_net without a rule of its own — a secret is an egress grant as well as
a credential. It is a by-name dial like any hostname rule, so a host answering
with a private, loopback or CGNAT address is still refused unless an IP or CIDR
rule covers that range. Those hosts on port 80, on any other port, or over UDP
are governed by allow_net alone.
Handle to a running or stopped box.
| Property | Type | Description |
|---|---|---|
id |
str |
Unique box identifier (ULID format) |
network |
NetworkHandle |
Box-scoped tunnel operations |
ssh |
SshHandle |
SSH control (configure/status/disable) |
| Method | Signature | Description |
|---|---|---|
exec() |
(cmd, args, env, tty) -> Execution |
Execute command (async) |
attach() |
(execution_id=None, stdin=True) -> Execution |
Follow a running session (async) |
stop() |
() -> None |
Stop the box gracefully (async) |
info() |
async () -> BoxInfo |
Get box metadata |
metrics() |
() -> BoxMetrics |
Get resource usage metrics (async) |
attach() with no argument follows the box's main command — the container
init, which is what run IMAGE COMMAND starts. Pass execution_id to
reattach to an exec() session instead.
attach(stdin=False) attaches read-only — docker's --no-stdin. The returned
execution exposes no stdin, and against a remote runtime the server refuses
stdin, signal and resize sent over that socket. Use it to follow a box's
output without a keystroke reaching the workload.
The guarantee is scoped to the attach stream, as it is in docker: holding a box
handle still lets you stop() it, and Execution.kill() / .signal() /
.resize_tty() go over their own routes rather than this socket.
Metadata about a box.
| Field | Type | Description |
|---|---|---|
id |
str |
Unique box identifier (ULID) |
name |
str | None |
Optional user-assigned name |
state |
BoxStateInfo |
Runtime state with status, running, and nullable pid and exit_code fields. exit_code is how the main command ended — its own code, or 128 + n when a signal ended it; stopping a box signals that command, so a stop is recorded here too; 0 is a real value, so test for None rather than falsiness |
created_at |
str |
ISO 8601 creation timestamp |
started_at |
str | None |
Time when the box most recently entered Running (RFC 3339); None if not recorded or unavailable over REST |
last_activity_at |
str | None |
Time the box was last active (RFC 3339), the clock AutoStop measures idleness against; None for a local box, which records no activity |
image |
str |
OCI image used |
cpus |
int |
Allocated CPU cores |
memory_mib |
int |
Allocated memory in MiB |
network |
NetworkInfo | None |
Current network configuration and resolved local publications |
NetworkInfo has outbound: OutboundNetworkInfo, inbound: InboundNetworkInfo, and published_ports: List[PublishedPort] | None; each
direction has mode: str and allow_net: List[str]. The pre-split
mode: str / allow_net: List[str] attributes remain readable on
NetworkInfo as views onto outbound. Each PublishedPort has named
guest_port, host_ip, host_port, and protocol attributes.
network is None means network information is unavailable. Within
NetworkInfo, published_ports is None means the current handle does not know
the lifecycle's publications, [] means there are no active publications, and
a populated list contains concrete active local bindings. BoxOptions.ports
remains the request API; its optional host port is not reused in resolved output.
box.info() has no synchronous variant. Local metadata reads report
bindings captured when this handle started or reattached the box; REST metadata
reads fetch the current server record. A newly loaded local running box has no
live binding data yet, so box, get, or list info may report
published_ports is None until an operation requiring live state reattaches it.
Detailed state information for a box.
| Value | Description |
|---|---|
Created |
Box created but not yet started |
Starting |
Box is initializing |
Running |
Box is running and ready |
Stopping |
Box is shutting down |
Stopped |
Box is stopped |
Failed |
Box encountered an error |
Both boxlite.Box and SyncBox expose box.ssh.
| Operation | Signature | Description |
|---|---|---|
| Configure | await box.ssh.configure(config) -> SshStatus |
Apply SSH configuration |
| Query | await box.ssh.status() -> SshStatus |
Return SSH status |
| Disable | await box.ssh.disable() -> SshStatus |
Disable SSH |
If a box is not running and explicitly sets BoxOptions.cmd or entrypoint,
call await box.start() before configuring SSH; otherwise configure() raises
InvalidStateError. See the SSH lifecycle guide for
implicit startup and stopped-box behavior.
from boxlite import SshAccount, SshConfig
config = SshConfig(
listen_address="0.0.0.0:2222",
host_private_key=open("host_key").read(),
accounts=[SshAccount(login="alice", authorized_keys=[open("id_ed25519.pub").read()])],
)
status = await box.ssh.configure(config)In the sync API the same calls drop await: box.ssh.configure(config).
SshConfig(listen_address, host_private_key, accounts=[]) accepts
SshAccount(login, authorized_keys=[], ca=None) entries; certificate
authentication uses SshCaConfig(public_key, principal). Credential-bearing
objects redact keys in repr().
Configuration getters return copies: config.accounts, each account's
authorized_keys, and its nested ca must be assigned back after editing.
For example, config.accounts[0].authorized_keys.append(key) does not update
config. This follows PyO3's field-copying rules.
accounts = config.accounts
account = accounts[0]
keys = account.authorized_keys
keys.append(open("new_user.pub").read())
account.authorized_keys = keys
ca = account.ca
if ca is not None:
ca.principal = "updated-principal"
account.ca = ca
config.accounts = accounts
status = await box.ssh.configure(config)SshStatus exposes enabled: bool, generation: int, listen_address,
host_public_key, and host_key_fingerprint. generation is a Python int
that preserves the full unsigned 64-bit value.
Both boxlite.Box and boxlite.SimpleBox expose box.network.
| Operation | Signature | Description |
|---|---|---|
| Tunnel | await box.network.tunnel(target, options=None) -> BoxTunnel |
Prepare a tunnel; target is a socket address tuple (ip, port) (loopback or the guest IP), options is TunnelOptions(is_public=False) for a private cloud tunnel, public by default |
| Token | tunnel.token() -> str | None |
Full on creation, masked on later preparations; is_public() reports the mode |
| Revoke | await box.network.revoke_tunnel(port) -> None |
Withdraw the port's tunnel; open connections stay open |
| Forward | await tunnel.forward(listen) -> TunnelForwarder |
Bind one local listener and forward every client |
| Inspect | tunnel.uri() -> str | None |
Read the prepared public URL; None for a local box |
| Connect | await tunnel.connect() -> BoxConnection |
Consume the prepared tunnel into its byte stream |
| Read/write | await connection.read(max_bytes), await connection.write(data) |
Exchange bytes with the service |
| Close | await connection.close() |
Close the connection |
Creation accepts (ip, port) and IPv6 (ip, port, 0, 0) tuples. IPs must be
numeric; hostnames and nonzero IPv6 flowinfo/scope_id are rejected. Bare ports and
SocketAddress objects are no longer creation arguments.
For the separate forward(listen) operation, use SocketAddress.tcp(host="127.0.0.1", port=0) or
SocketAddress.unix(path). A forwarder exposes local_addr(), await wait(), and repeatable await close(); synchronous and SimpleBox wrappers
provide the same operations.
Each BoxTunnel is one-shot: choose connect() or forward(). Forwarding opens
authenticated connections against the existing declaration and cannot undo revocation. This differs
from BoxOptions.ports, which creates a persistent, local-only host listener
that accepts repeated connections from ordinary host applications.
Represents a running command execution.
| Method | Signature | Description |
|---|---|---|
stdout() |
() -> ExecStdout |
Get stdout stream (async iterator) |
stderr() |
() -> ExecStderr |
Get stderr stream (async iterator) |
stdin() |
() -> ExecStdin |
Get stdin writer |
wait() |
() -> ExecResult |
Wait for completion (async) |
kill() |
(signal: int = 9) -> None |
Send signal to process (async) |
resize_tty() |
(rows: int, cols: int) -> None |
Resize PTY terminal for executions started with tty=True (async) |
# Execute with streaming output
execution = await box.exec("python", ["-c", "for i in range(5): print(i)"])
# Stream stdout
async for line in execution.stdout():
print(f"Output: {line}")
# Wait for completion
result = await execution.wait()
print(f"Exit code: {result.exit_code}")Async iterators for streaming output.
# Stream stdout line by line
stdout = execution.stdout()
async for line in stdout:
print(line)
# Stream stderr
stderr = execution.stderr()
async for line in stderr:
print(f"Error: {line}", file=sys.stderr)Note: Each stream can only be iterated once. After iteration, the stream is consumed.
Writer for sending input to a running process.
| Method | Signature | Description |
|---|---|---|
send_input() |
(data: bytes) -> None |
Write bytes to stdin (async) |
# Interactive input
execution = await box.exec("cat")
stdin = execution.stdin()
# Send data
await stdin.send_input(b"Hello\n")
await stdin.send_input(b"World\n")
# Wait for completion
result = await execution.wait()Result of a completed execution.
| Field | Type | Description |
|---|---|---|
exit_code |
int |
Process exit code (0 = success) |
Note: For higher-level APIs (SimpleBox.exec()), the result also includes stdout and stderr strings.
Context manager for basic command execution with automatic cleanup.
from boxlite import SimpleBoxSimpleBox(
image: str,
memory_mib: int = None,
cpus: int = None,
runtime: Boxlite = None,
name: str = None,
auto_remove: bool = True,
**kwargs
)| Parameter | Type | Default | Description |
|---|---|---|---|
image |
str |
Required | Container image to use |
memory_mib |
int |
System default | Memory limit in MiB |
cpus |
int |
System default | Number of CPU cores |
runtime |
Boxlite |
Global default | Runtime instance |
name |
str |
None | Optional unique name |
auto_remove |
bool |
True |
Remove box when stopped |
**kwargs |
Additional options: env, volumes, ports, working_dir, network, secrets |
| Property | Type | Description |
|---|---|---|
id |
str |
Box ID (raises if not started) |
| Method | Signature | Description |
|---|---|---|
start() |
() -> Self |
Explicitly start the box (async) |
exec() |
(cmd, *args, env=None, user=None, timeout=None, cwd=None) -> ExecResult |
Execute command and wait (async) |
info() |
async () -> BoxInfo |
Get box metadata |
shutdown() |
() -> None |
Shutdown and release resources |
async with SimpleBox(image="python:slim") as box:
result = await box.exec("python", "-c", "print('Hello!')")
print(result.stdout) # "Hello!\n"
print(result.exit_code) # 0
# Per-command options
result = await box.exec("pwd", cwd="/tmp") # working directory
result = await box.exec("whoami", user="nobody") # run as user
result = await box.exec("sleep", "60", timeout=5) # timeout in secondsSpecialized box for Python code execution with package management.
from boxlite import CodeBoxCodeBox(
image: str = "python:slim",
memory_mib: int = None,
cpus: int = None,
runtime: Boxlite = None,
**kwargs
)| Method | Signature | Description |
|---|---|---|
run() |
(code: str, timeout: int = None) -> str |
Execute Python code (async) |
run_script() |
(script_path: str) -> str |
Execute Python script file (async) |
install_package() |
(package: str) -> str |
Install package with pip (async) |
install_packages() |
(*packages: str) -> str |
Install multiple packages (async) |
async with CodeBox() as cb:
# Install packages
await cb.install_package("requests")
# Run code
result = await cb.run("""
import requests
print(requests.get('https://api.github.com/zen').text)
""")
print(result)Box configured for browser automation with Chrome DevTools Protocol.
from boxlite import BrowserBox, BrowserBoxOptions| Field | Type | Default | Description |
|---|---|---|---|
browser |
str |
"chromium" |
Browser type: "chromium", "firefox", "webkit" |
memory |
int |
2048 |
Memory in MiB |
cpu |
int |
2 |
Number of CPU cores |
| Browser | Port |
|---|---|
| chromium | 9222 |
| firefox | 9223 |
| webkit | 9224 |
| Method | Signature | Description |
|---|---|---|
endpoint() |
() -> str |
Get CDP endpoint URL |
from boxlite import BrowserBox, BrowserBoxOptions
opts = BrowserBoxOptions(browser="chromium", memory=4096)
async with BrowserBox(opts) as browser:
endpoint = browser.endpoint() # "http://localhost:9222"
# Connect with Puppeteer or Playwright
# puppeteer.connect({ browserURL: endpoint })Box with full desktop environment and GUI automation capabilities.
from boxlite import ComputerBoxComputerBox(
cpu: int = 2,
memory: int = 2048,
gui_http_port: int = 3000,
gui_https_port: int = 3001,
runtime: Boxlite = None,
**kwargs
)| Method | Signature | Description |
|---|---|---|
mouse_move() |
(x: int, y: int) -> None |
Move cursor to coordinates (async) |
left_click() |
() -> None |
Left click at current position (async) |
right_click() |
() -> None |
Right click at current position (async) |
middle_click() |
() -> None |
Middle click at current position (async) |
double_click() |
() -> None |
Double left click (async) |
triple_click() |
() -> None |
Triple left click (async) |
left_click_drag() |
(start_x, start_y, end_x, end_y) -> None |
Drag from start to end (async) |
cursor_position() |
() -> Tuple[int, int] |
Get current cursor (x, y) (async) |
| Method | Signature | Description |
|---|---|---|
type() |
(text: str) -> None |
Type text characters (async) |
key() |
(text: str) -> None |
Press key or key combination (async) |
The key() method uses xdotool key syntax:
| Key | Syntax |
|---|---|
| Enter | Return |
| Tab | Tab |
| Escape | Escape |
| Backspace | BackSpace |
| Delete | Delete |
| Arrow keys | Up, Down, Left, Right |
| Function keys | F1, F2, ... F12 |
| Modifiers | ctrl, alt, shift, super |
| Combinations | ctrl+c, ctrl+shift+s, alt+Tab |
Examples:
await computer.key("Return") # Press Enter
await computer.key("ctrl+c") # Copy
await computer.key("ctrl+shift+s") # Save As
await computer.key("alt+Tab") # Switch window
await computer.key("ctrl+a Delete") # Select all and delete| Method | Signature | Description |
|---|---|---|
wait_until_ready() |
(timeout: int = 60) -> None |
Wait for desktop ready (async) |
screenshot() |
() -> dict |
Capture screen (async) |
scroll() |
(x, y, direction, amount=3) -> None |
Scroll at position (async) |
get_screen_size() |
() -> Tuple[int, int] |
Get screen dimensions (async) |
{
"data": str, # Base64-encoded PNG
"width": int, # 1024 (default)
"height": int, # 768 (default)
"format": str # "png"
}| Direction | Description |
|---|---|
"up" |
Scroll up |
"down" |
Scroll down |
"left" |
Scroll left |
"right" |
Scroll right |
async with ComputerBox() as desktop:
await desktop.wait_until_ready()
# Take screenshot
screenshot = await desktop.screenshot()
# Mouse interaction
await desktop.mouse_move(100, 200)
await desktop.left_click()
# Type text
await desktop.type("Hello, World!")
await desktop.key("Return")
# Get screen size
width, height = await desktop.get_screen_size()Box for interactive terminal sessions with PTY support.
from boxlite import InteractiveBoxInteractiveBox(
image: str,
shell: str = "/bin/sh",
tty: bool = None,
memory_mib: int = None,
cpus: int = None,
runtime: Boxlite = None,
name: str = None,
auto_remove: bool = True,
**kwargs
)| Parameter | Type | Default | Description |
|---|---|---|---|
image |
str |
Required | Container image |
shell |
str |
"/bin/sh" |
Shell to run |
tty |
bool | None |
None |
TTY mode (see below) |
| Value | Behavior |
|---|---|
None |
Auto-detect from sys.stdin.isatty() |
True |
Force TTY mode with I/O forwarding |
False |
No I/O forwarding (programmatic control) |
| Method | Signature | Description |
|---|---|---|
wait() |
() -> None |
Wait for shell to exit (async) |
# Interactive shell session
async with InteractiveBox(image="alpine:latest") as box:
# You're now in an interactive shell
# Type commands, see output in real-time
# Type "exit" to close
await box.wait()Synchronous wrappers using greenlet fiber switching. Requires pip install boxlite[sync].
from boxlite import SyncBoxlite, SyncBox, SyncSimpleBox, SyncCodeBox| Use Case | API |
|---|---|
| New async applications | Async API (default) |
| Existing sync codebase | Sync API |
| Jupyter notebooks | Sync API |
| REPL/interactive use | Sync API |
| Inside async functions | Async API only |
| Async API | Sync API | Notes |
|---|---|---|
Boxlite |
SyncBoxlite |
get_info() and list_info() are async-only |
Box |
SyncBox |
Box.info() is async-only and is not exposed by SyncBox |
Execution |
SyncExecution |
|
ExecStdout |
SyncExecStdout |
Regular iterator |
ExecStderr |
SyncExecStderr |
Regular iterator |
SimpleBox |
SyncSimpleBox |
SimpleBox.info() is async-only and is not exposed by SyncSimpleBox |
CodeBox |
SyncCodeBox |
Inherits the async-only metadata rule |
RegistryHandle |
SyncRegistryHandle |
from boxlite import SyncBoxlite, BoxOptions
with SyncBoxlite.default() as runtime:
box = runtime.create(BoxOptions(image="alpine:latest"))
execution = box.exec("echo", ["Hello"])
for line in execution.stdout():
print(line)
box.stop()SyncBoxlite.rest(options) mirrors Boxlite.rest(...):
from boxlite import BoxliteRestOptions, SyncBoxlite
with SyncBoxlite.rest(BoxliteRestOptions.from_env()) as runtime:
print(runtime.images.usage())
print(runtime.images.registries.list())from boxlite import SyncSimpleBox
with SyncSimpleBox(image="python:slim") as box:
result = box.exec("python", "-c", "print('Hello!')")
print(result.stdout)from boxlite import SyncCodeBox
with SyncCodeBox() as cb:
result = cb.run("print('Hello, World!')")
print(result)The sync API uses greenlet fiber switching:
- A dispatcher fiber runs the asyncio event loop
- User code runs in the main fiber
- Sync methods switch to dispatcher, await async operations, then switch back
Limitation: Cannot be used inside an async context (when an event loop is already running).
from boxlite import BoxliteError, ExecError, TimeoutError, ParseErrorBoxliteError (base)
├── ExecError # Command execution failed
├── TimeoutError # Operation timed out
├── ParseError # Output parsing failed
└── NotFoundError, InvalidStateError, … # Runtime failures, see below
A failure the native runtime reports is raised as one class per kind of
failure, each with a code. The names and codes are the ones
BoxliteError::http() gives (src/shared/src/errors.rs), so a REST server
answers the same failure with the same code. Every class is also a
RuntimeError, which is what these failures were raised as before.
| Class | code |
Raised when |
|---|---|---|
NotFoundError |
not_found |
The box, image, volume or snapshot does not exist |
AlreadyExistsError |
already_exists |
The name is taken |
InvalidStateError |
invalid_state |
The resource's state does not allow the operation |
InvalidArgumentError |
invalid_argument |
A value was refused |
UnsupportedError |
unsupported |
The operation is not available on this runtime |
ResourceExhaustedError |
resource_exhausted |
A limit was reached |
StoppedError |
stopped |
The box has stopped |
SessionReapedError |
session_reaped |
The session was reaped |
ImageError |
image_pull_failed |
An image could not be pulled or read |
ExecutionError |
execution_failed |
A process could not be run |
NetworkError |
network_unavailable |
The network was unavailable |
UpstreamUnavailableError |
upstream_unavailable |
A component did not answer |
EngineError |
engine_unavailable |
The virtualization engine failed |
ConfigError |
config_error |
Configuration is invalid, or authentication failed |
StorageError |
storage_error |
Storage could not be read or written |
DatabaseError |
database_error |
The runtime's database failed |
MetadataError |
metadata_error |
Stored metadata could not be read |
InternalError |
internal |
An unexpected failure |
from boxlite import NotFoundError
try:
await runtime.remove("no-such-box")
except NotFoundError as e:
print(e.code) # not_foundBase exception for all BoxLite errors.
try:
async with SimpleBox(image="invalid:image") as box:
pass
except BoxliteError as e:
print(f"BoxLite error: {e}")Raised when a command execution fails (non-zero exit code).
| Attribute | Type | Description |
|---|---|---|
command |
str |
The command that failed |
exit_code |
int |
Non-zero exit code |
stderr |
str |
Standard error output |
try:
result = await box.exec("false") # Exit code 1
except ExecError as e:
print(f"Command: {e.command}")
print(f"Exit code: {e.exit_code}")
print(f"Stderr: {e.stderr}")Raised when an operation times out.
try:
await computer.wait_until_ready(timeout=5)
except TimeoutError:
print("Desktop did not become ready in time")Raised when output parsing fails.
try:
x, y = await computer.cursor_position()
except ParseError:
print("Failed to parse cursor position")Aggregate metrics across all boxes.
| Field | Type | Description |
|---|---|---|
boxes_created |
int |
Total boxes created |
boxes_destroyed |
int |
Total boxes destroyed |
total_exec_calls |
int |
Total command executions |
active_boxes |
int |
Currently running boxes |
runtime = Boxlite.default()
metrics = await runtime.metrics()
print(f"Boxes created: {metrics.boxes_created}")
print(f"Active boxes: {metrics.active_boxes}")Per-box resource usage metrics.
| Field | Type | Description |
|---|---|---|
cpu_time_ms |
int |
Total CPU time in milliseconds |
memory_usage_bytes |
int |
Current memory usage in bytes |
network_bytes_sent |
int |
Total bytes sent |
network_bytes_received |
int |
Total bytes received |
metrics = await box.metrics()
print(f"CPU time: {metrics.cpu_time_ms}ms")
print(f"Memory: {metrics.memory_usage_bytes / (1024**2):.2f} MB")Default values used by BoxLite.
| Constant | Value | Description |
|---|---|---|
DEFAULT_CPUS |
1 |
Default CPU cores |
DEFAULT_MEMORY_MIB |
2048 |
Default memory in MiB |
COMPUTERBOX_CPUS |
2 |
ComputerBox default CPUs |
COMPUTERBOX_MEMORY_MIB |
2048 |
ComputerBox default memory |
COMPUTERBOX_DISPLAY_WIDTH |
1024 |
Screen width in pixels |
COMPUTERBOX_DISPLAY_HEIGHT |
768 |
Screen height in pixels |
COMPUTERBOX_GUI_HTTP_PORT |
3000 |
HTTP GUI port |
COMPUTERBOX_GUI_HTTPS_PORT |
3001 |
HTTPS GUI port |
DESKTOP_READY_TIMEOUT |
60 |
Desktop ready timeout (seconds) |
- Python SDK README - Quick start and examples
- Getting Started Guide - Installation
- Configuration Reference - BoxOptions details
- Error Codes - Error handling