Complete API reference for the BoxLite Python SDK.
Python: 3.10+ Platforms: macOS (Apple Silicon), Linux (x86_64, ARM64)
- Runtime Management
- Box Handle
- 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 |
# 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}")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 to host CPU count) |
memory_mib |
int |
512 |
Memory limit in MiB (128-65536) |
disk_size_gb |
int | None |
None |
Persistent disk size in GB (None = ephemeral) |
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_remove |
bool |
True |
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(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 |
| 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) |
remove() |
() -> None |
Delete box and its data (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 fields |
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 boxlite.SimpleBox expose box.network.
| Operation | Signature | Description |
|---|---|---|
| Tunnel | await box.network.tunnel(port) -> BoxTunnel |
Prepare a one-shot tunnel to a TCP service in the box |
| 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 |
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(). 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 |
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()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
Base 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