Complete reference for BoxLite APIs, configuration, and error handling.
Complete API documentation for each SDK:
| SDK | Documentation | Description |
|---|---|---|
| Python | Python API Reference | Async/sync API, box types, metrics |
| Node.js | Node.js API Reference | TypeScript definitions, box types, CDP endpoints |
| Rust | Rust API Reference | Core runtime, stream APIs, security options |
| C | C API Reference | FFI bindings, typed options, callback streaming |
| Surface | Documentation | Description |
|---|---|---|
boxlite |
CLI Reference | All subcommands, global flags, volume/port grammar, installation & verification, exit codes |
| Surface | Documentation | Description |
|---|---|---|
| Box API | openapi/box.openapi.yaml |
The portable REST contract, 26 paths. Groups: configuration & discovery, authentication, volumes, boxes, box lifecycle, snapshot/portability, execution, files, network, metrics, images |
| Reference server | openapi/reference-server/ |
A partial implementation used as a client test fixture, not a conformance target |
The spec is the contract, not an inventory of any one server, and no implementation currently serves all 26 paths:
| Server | Serves | Does not serve |
|---|---|---|
boxlite serve |
boxes, lifecycle, exec, files, snapshots, clone/export/import, metrics, config, me | network/tunnel, the three images/* paths. Volume routes are registered but every operation answers 400 UnsupportedError |
| reference server | boxes, lifecycle, exec, files, snapshots, clone/export/import, metrics, config, me | volumes, images, network/tunnel, attach, and DELETE …/executions/{exec_id} (kill) |
Read each server's routes rather than either README — both under-report.
GET /config does not close this gap: it carries feature flags
(tty_enabled, streaming_enabled, snapshots_enabled, clone_enabled,
export_enabled, import_enabled), not route availability, has no flag at all
for volumes, images or tunnel, and can disagree with the routes — the reference
server advertises streaming_enabled: true while serving no attach route.
{prefix} is a deployment-defined routing slot, opaque to the client, published
by a server as Principal.path_prefix. A single-tenant server may omit the
segment entirely — that is the contract's null-prefix case, and what boxlite serve does, so its /v1/boxes/… routes are conformant. The field name is not:
both servers serialize it as prefix (serve/handlers/me.rs,
reference-server/server.py) where the contract says path_prefix. The Rust
client sidesteps discovery altogether and takes path_prefix from its own
options. Treat the segment as server-supplied configuration; never hardcode one.
A deployed BoxLite platform also runs services of its own — control plane,
runner, preview proxy, telemetry collector — catalogued separately in
apps/API.md. Those are deployment internals, not part of
the portable contract above.
For complete Python API documentation, see Python API Reference.
Key Classes:
| Class | Description |
|---|---|
Boxlite |
Main runtime for creating and managing boxes |
Box |
Handle to a running or stopped box |
SimpleBox |
Context manager for basic execution |
CodeBox |
Specialized box for Python code execution |
BrowserBox |
Box configured for browser automation |
ComputerBox |
Box with desktop automation capabilities |
InteractiveBox |
Box for interactive shell sessions |
For complete Node.js API documentation, see Node.js API Reference.
Key Classes:
| Class | Description |
|---|---|
SimpleBox |
Basic container for command execution |
CodeBox |
Python code execution sandbox |
BrowserBox |
Browser automation with CDP endpoint |
ComputerBox |
Desktop automation (14 methods) |
InteractiveBox |
PTY terminal sessions |
For complete Rust API documentation, see Rust API Reference.
Core Types:
use boxlite::{
BoxliteRuntime, // Main runtime
BoxOptions, // Box configuration
LiteBox, // Box handle
BoxCommand, // Command builder
RootfsSpec, // Rootfs specification
VolumeSpec, // Volume mount specification
PortSpec, // Port forwarding specification
};For complete C API documentation, see C API Reference.
Functions:
| Function | Description |
|---|---|
boxlite_runtime_new |
Create runtime instance |
boxlite_create_box |
Create a new box |
boxlite_execute |
Run command with streaming |
boxlite_stop_box |
Stop and free box |
For the complete CLI reference, see CLI Reference.
Common subcommands:
| Command | Description |
|---|---|
boxlite run |
Create a box from an image and run a command |
boxlite exec |
Run a command inside a running box |
boxlite list |
List boxes (aliases: ls, ps) |
boxlite cp |
Copy files between host and box |
boxlite inspect |
Show detailed box info (JSON, YAML, or Go template) |
boxlite serve |
Start the long-running REST API server |
Complete reference for box configuration options.
OCI image URI to use for the box rootfs.
Format: [registry/]repository[:tag]
Default: "python:slim" (for Python SDK convenience wrappers)
Examples:
# Docker Hub (default registry)
image="python:3.11-slim"
image="alpine:latest"
image="ubuntu:22.04"
# GitHub Container Registry
image="ghcr.io/owner/repo:tag"
# Amazon ECR
image="123456.dkr.ecr.us-east-1.amazonaws.com/repo:tag"
# Google Container Registry
image="gcr.io/project/image:tag"Notes:
- Images are pulled on first use and cached in
~/.boxlite/images/ - Layer-level caching for fast subsequent starts
- Authentication: Use registry-specific auth (Docker credentials, etc.)
Structured network configuration for outbound connectivity.
Default: omitted, which behaves like:
{
"mode": "enabled",
"allow_net": []
}Shape:
mode:"enabled"or"disabled"allow_net: optional outbound allowlist used only whenmode="enabled"
Notes:
"enabled"gives the guest outbound connectivity."disabled"removes the guest network interface entirely.- Empty or omitted
allow_netmeans full outbound access. - A non-empty
allow_netrestricts both TCP and UDP egress. IP and CIDR rules match the destination address and apply to both. Hostname rules are enforced by inspecting TLS SNI / HTTP Host, which only TCP carries, so anallow_netholding only hostnames denies all UDP egress — otherwise a guest could sidestep the rule by addressing an allowed name's IP directly. QUIC/HTTP3 to such a host falls back to TCP; add the IP or CIDR to keep UDP open. - A hostname rule authorizes the name, not an address. Once the SNI / Host
matches, the gateway resolves the name itself through the host resolver and
connects to its current address; the address the guest connected to is
ignored. A host that changes IP while the box runs stays reachable, each
subdomain under a wildcard resolves to its own address, and a guest that
hard-codes another IP with an allowed SNI still reaches the allowed host. A
name that resolves to a private, loopback or CGNAT address is not connected
to unless an IP or CIDR rule also lists that range. Link-local (metadata,
169.254.0.0/16) is refused outright on both paths: no rule re-admits it, by name or by address. The box's own subnet is refused only as a resolved candidate, so that a name can never route back into the virtual network — a guest that addresses192.168.127.254itself still reaches it under an IP or CIDR rule covering it, ashost.boxlite.internalbelow describes. - DNS inside the box is not restricted.
allow_netis a connection-layer control:/etc/resolv.confpoints at the gateway192.168.127.1, which forwards every query, listed name or not, to the host's resolver. A denied destination therefore resolves to its real address and fails when the guest connects, not when it looks the name up. A denied name previously resolved to0.0.0.0, so code that detected the block by inspecting a lookup result must check for a failed connection instead. - A host named by
secretsbelow is reachable on port 443 without anallow_netrule of its own. Substitution runs in front of the allowlist, and the connection is dialed by name like any hostname rule, so the guest cannot steer it elsewhere by choosing an address. Under a non-emptyallow_netthat dial also refuses an answer in a private, loopback or CGNAT range unless an IP or CIDR rule covers it; the link-local and box-subnet refusals above apply either way. Only 443 is covered: plain HTTP, any other port, and UDP to that same host still need a rule. Under"disabled"a secret grants nothing, since the guest has no network interface at all. - The gateway's DNS resolver and DHCP are internal services and stay reachable
regardless of
allow_net. host.boxlite.internalis a built-in hostname that resolves to192.168.127.254and reaches host loopback services. The DNS record is always served, but egress to that address is governed byallow_net: add"192.168.127.254", or a CIDR covering it, to reach it under a non-empty allowlist.- Security:
allow_netis not the only egress gate — declaring a secret for a host opens port 443 to it, as above. With an empty or omittedallow_net, any service bound to host loopback is reachable from inside the box viahost.boxlite.internalor192.168.127.254; allowing that address opens all of them. Andallow_netbounds where the guest can connect, not what it can look up: every query it makes reaches the host's resolver, denied names included, so a query name is a channel out of the box. Earlier versions answered denied names0.0.0.0locally, which narrowed that channel for A queries but never closed it, since other record types were forwarded regardless. Treatallow_netas a routing control, not a containment boundary.
Supported patterns:
- Exact hostname:
"api.openai.com" - Wildcard hostname:
"*.example.com" - Exact IP:
"192.168.1.10" - CIDR range:
"10.0.0.0/8"
Host-side secret substitution rules for outbound HTTPS requests.
Shape:
name: human-readable secret namevalue: real secret valuehosts: matching hosts for substitutionplaceholder: optional guest-visible token, defaults to<BOXLITE_SECRET:{name}>
Notes:
- The guest sees only the placeholder, never the real secret value.
- The placeholder is also exposed as
BOXLITE_SECRET_<NAME>inside the guest. - A secret is an egress grant as well as a credential. Every host this
matches becomes reachable on port 443 without a rule of its own — declaring
that a credential is used at a host already says the host must be reachable.
A wildcard entry grants its subdomains the same way it matches them, one
level deep. The grant is a by-name dial, so under a non-empty
allow_neta host answering with a private, loopback or CGNAT address is refused unless an IP or CIDR rule covers that range. Nothing else opens: those hosts on port 80, on any other port, or over UDP are still governed byallow_netalone. - Substitution is HTTPS-only. A plain-HTTP request would carry the placeholder rather than the real value, which is the other reason port 80 is not opened here.
- Under
mode"disabled"a secret does nothing: no network backend is created, so nothing is substituted and no host becomes reachable. The placeholder environment variable is still injected into the guest. - C SDK users configure secrets with
boxlite_options_add_secret().
Linux capability overrides for the container's init and all later exec processes. Both lists default to empty, preserving BoxLite's Docker-compatible 14-capability baseline.
- Names are case-insensitive and accept either
NET_ADMINorCAP_NET_ADMIN. ALLis supported in either list.- Without
ALL, drops are applied to the baseline before explicit additions, so the same specifically named capability in both lists remains enabled. - With
add=["ALL"], explicit drops win, matching Docker. - Use
drop=["ALL"]with explicit additions to construct a minimal set. - Malformed names are rejected at the API boundary. A well-formed name that the bundled guest runtime does not support is rejected during container initialization.
- A remote client and the host both check support before creating a box; a custom policy is rejected rather than ignored when either side is too old.
- The resolved set is applied to OCI bounding, effective, and permitted sets.
Inheritable and ambient capabilities stay unset; they are not implied by
add.
Number of CPU cores allocated to the box.
Default: 1
Range: 1 to host CPU count
Example:
cpus=2 # 2 CPU cores
cpus=4 # 4 CPU coresNotes:
- CPU scheduling is proportional (shares-based)
- Does not reserve physical cores, just scheduling weight
- Monitor actual usage with
box.metrics().cpu_time_ms
Memory limit in mebibytes (MiB).
Default: 512
Range: 128 to 65536 (64 GiB)
Example:
memory_mib=1024 # 1 GB
memory_mib=2048 # 2 GB
memory_mib=4096 # 4 GBNotes:
- 1 MiB = 1024 KiB = 1,048,576 bytes
- Minimum 128 MiB required for most images
- Out of memory kills the box process
- Monitor with
box.metrics().memory_usage_bytes
Create a persistent QCOW2 disk image.
Default: None (ephemeral storage only)
Range: 1 to 1024 (1 TB)
Example:
disk_size_gb=None # Ephemeral (default)
disk_size_gb=10 # 10 GB persistent disk
disk_size_gb=100 # 100 GB persistent diskNotes:
- Disk persists across stop/restart
- Stored at
~/.boxlite/boxes/{box-id}/disk.qcow2 - Copy-on-write (thin provisioned)
- Deleted when box is removed
Working directory for command execution inside the box.
Default: "/root"
Example:
working_dir="/app"
working_dir="/home/user/project"Notes:
- Directory must exist in the container image
- Commands execute with this as
$PWD
Environment variables as (key, value) pairs.
Default: [] (inherit from image)
Example:
env=[
("DATABASE_URL", "postgresql://localhost/db"),
("API_KEY", "secret"),
("DEBUG", "true"),
("PATH", "/custom/bin:/usr/bin:/bin"), # Override PATH
]Notes:
- Variables are appended to image environment
- Use to override image defaults (e.g.,
PATH) - Sensitive values (API keys, passwords) are visible in box metadata
Volume mounts. A tuple is always a host bind; a dict takes either
managed_volume (a volume's id or name) or host_path, never both.
Format: (host_path, guest_path[, read_only]) - read_only is a bool, default False.
Default: [] (no mounts)
Example:
volumes=[
# Read-only mount (data input)
("/host/config", "/etc/app/config", True),
# Read-write mount (data output)
("/host/data", "/mnt/data", False),
# Home directory mount
(os.path.expanduser("~/Documents"), "/mnt/docs", True),
]Notes:
- Uses virtiofs for high-performance file sharing
- Host path must exist before box creation
- Guest path is created automatically if missing
- Changes to
rwmounts are visible on host immediately
Port forwarding as (host_port, guest_port, protocol) tuples.
Format: (host_port, guest_port, "tcp"), or a dictionary with
guest_port and no host_port to request an automatic host port.
Default: [] (no port forwarding)
Example:
ports=[
(8080, 80, "tcp"), # HTTP
(8443, 443, "tcp"), # HTTPS
(5432, 5432, "tcp"), # PostgreSQL
(3000, 8000, "tcp"), # Custom mapping
{"guest_port": 3000}, # OS-selected host port
]Notes:
- Uses gvproxy for NAT port mapping
- Host port must be available (not in use)
- Fixed host ports must be unique and available
- TCP is supported; UDP is rejected
- Port publication is local-only and owns a listener that accepts repeated
connections. For portable local/remote SDK code, use
box.network.tunnel(port); each returned tunnel is one-shot. - OCI
EXPOSEis metadata and does not publish a host port. - Port mappings are only for host → box traffic. Use
host.boxlite.internal:<port>for box → host loopback traffic.
Automatically remove box when stopped.
Default: True
Example:
auto_remove=True # Auto cleanup (default)
auto_remove=False # Manual cleanup requiredNotes:
True: Box is removed when context exits orstop()is calledFalse: Box persists after stop, can be restarted withruntime.get(box_id)- Manual cleanup:
await box.remove()
Base directory for BoxLite runtime data.
Default: ~/.boxlite
Override: Set BOXLITE_HOME environment variable
Structure:
~/.boxlite/
├── images/ # OCI image cache (blobs, index.json)
├── boxes/ # Per-box data (config.json, disk.qcow2)
├── init/ # Shared guest rootfs
├── logs/ # Runtime logs
├── gvproxy/ # Network backend binaries
├── lock # Filesystem lock file
└── db/ # SQLite databases (boxes.db, images.db)
Example:
# Custom home directory
runtime = boxlite.Boxlite(boxlite.Options(home_dir="/custom/path"))Override default runtime home directory.
Default: ~/.boxlite
Example:
export BOXLITE_HOME=/custom/boxlite
python script.pyEnable debug logging for troubleshooting.
Levels: trace, debug, info, warn, error
Example:
# Debug logging
RUST_LOG=debug python script.py
# Trace logging (very verbose)
RUST_LOG=trace python script.py
# Module-specific logging
RUST_LOG=boxlite::runtime=debug python script.pyOverride temporary directory for BoxLite operations.
Default: System temp directory (/tmp on Linux/macOS)
Example:
export BOXLITE_TMPDIR=/custom/tmp
python script.pyCap on total decompressed bytes written while extracting OCI image layers.
A single extractor used for a whole image enforces this as a per-image cap;
per-layer extraction enforces it per layer. Exceeding the cap aborts the
pull with a resource_exhausted error (HTTP 429).
Default: 21474836480 (20 GiB)
Example:
# Allow larger images (e.g. ML images with huge layers)
export BOXLITE_MAX_LAYER_DECOMPRESSED_SIZE=53687091200
boxlite pull my-image:latestBoxLite uses a centralized error enum with specific variants for different error categories.
Platform or hypervisor not supported.
Cause:
- Running on Windows
- Running on Intel Mac
- KVM not available on Linux
- Hypervisor.framework not available on macOS
Example:
Error: unsupported engine kind
Solution:
- Use supported platform (macOS ARM64, Linux x86_64/ARM64)
- Verify hypervisor availability:
- Linux:
grep -E 'vmx|svm' /proc/cpuinfo - macOS: Ensure macOS 12+ on Apple Silicon
- Linux:
Hypervisor or VM engine error.
Cause:
- KVM module not loaded
- Insufficient permissions for
/dev/kvm - Hypervisor.framework error
- VM creation failed
Example:
Error: engine reported an error: KVM is not available
Solution:
# Linux: Load KVM module
sudo modprobe kvm kvm_intel # or kvm_amd
# Linux: Check /dev/kvm permissions
ls -l /dev/kvm
sudo chmod 666 /dev/kvm
# Linux: Add user to kvm group
sudo usermod -aG kvm $USER
# (logout and login required)Invalid box configuration.
Cause:
- Invalid CPU count (< 1 or > host CPUs)
- Invalid memory size (< 128 or > 65536)
- Invalid paths in volumes
- Invalid port numbers
Example:
Error: configuration error: CPU count must be between 1 and 8
Solution:
- Verify configuration parameters are within valid ranges
- Check file paths exist for volume mounts
- Ensure port numbers are valid (1-65535)
Filesystem or disk operation error.
Cause:
- Disk full (
~/.boxlitepartition) - Permission denied writing to
~/.boxlite - Disk image creation failed
- QCOW2 operation failed
Example:
Error: storage error: No space left on device
Solution:
# Check disk space
df -h ~/.boxlite
# Check permissions
ls -ld ~/.boxlite
chmod 755 ~/.boxlite
# Clean up old boxes
# (manually remove ~/.boxlite/boxes/*)OCI image pull or extraction error.
Cause:
- Network connectivity issues
- Invalid image name or tag
- Registry authentication required
- Image not found in registry
- Corrupted image layers
Example:
Error: images error: failed to pull image: 404 Not Found
Solution:
# Verify image exists
docker pull <image>
# Check network connectivity
ping registry-1.docker.io
# Authenticate for private images
docker login
# Clear image cache if corrupted
rm -rf ~/.boxlite/images/*Host-guest communication error (gRPC over vsock).
Cause:
- Guest agent not responding
- vsock connection failed
- gRPC timeout
- Guest initialization failed
Example:
Error: portal error: connection timeout
Solution:
- Enable debug logging:
RUST_LOG=debug - Check if box is running:
box.info().await?.status - Restart box:
box.stop()and recreate - Report issue with logs if persists
Network configuration or connectivity error.
Cause:
- gvproxy not running or crashed
- Port already in use
- Network backend initialization failed
Example:
Error: network error: bind: address already in use
Solution:
# Check port availability
lsof -i :8080
# Stop conflicting process or use different port
ports=[(8081, 80, "tcp")]
# Verify gvproxy binary exists
ls ~/.boxlite/gvproxy/Command execution error.
Cause:
- Command not found in image
- Command crashed or killed
- Execution timeout
- Streaming I/O error
Example:
Error: Execution error: command not found: python3
Solution:
- Verify command exists in image:
result = await box.exec("which", "python3")
- Check exit code and stderr:
result = await box.exec("command") if result.exit_code != 0: print(f"Failed: {result.stderr}")
Internal BoxLite error.
Cause:
- Unexpected internal state
- I/O error
- JSON parsing error
- Unhandled edge case
Example:
Error: internal error: unexpected state transition
Solution:
- Enable debug logging:
RUST_LOG=debug python script.py - Report issue with full logs to GitHub
- Include BoxLite version, platform, and reproduction steps
Box or resource not found.
Cause:
- Box ID doesn't exist
- Box was removed
- Image not in cache
Example:
Error: box not found: 01JJNH8...
Solution:
- List all boxes:
await runtime.list_info() - Verify box ID is correct
- Create new box if needed
Box or resource already exists.
Cause:
- Duplicate box creation attempt
- Port already forwarded
Example:
Error: already exists: box with this ID exists
Solution:
- Use existing box:
runtime.get(box_id) - Remove existing box:
box.remove() - Use different configuration (e.g., different port)
Box is in wrong state for requested operation.
Cause:
- Executing command on stopped box
- Stopping already stopped box
- Restarting box that never started
Example:
Error: invalid state: cannot execute on stopped box
Solution:
- Check box status:
info = await box.info(); print(info.state.status) - Restart box if stopped:
await runtime.get(box_id)(may auto-restart) - Create new box if needed
import boxlite
async def safe_execution():
try:
async with boxlite.SimpleBox(image="python:slim") as box:
result = await box.exec("python", "script.py")
# Check exit code
if result.exit_code != 0:
print(f"Command failed: {result.stderr}")
return
except Exception as e:
# All BoxLite errors are raised as Python exceptions
print(f"Error: {e}")
# Enable debug logging for details
# RUST_LOG=debug python script.pyuse boxlite::{BoxliteRuntime, BoxliteError, BoxliteResult};
fn main() -> BoxliteResult<()> {
let runtime = BoxliteRuntime::default_runtime();
match runtime.create(options) {
Ok((box_id, litebox)) => {
// Success
}
Err(BoxliteError::UnsupportedEngine) => {
eprintln!("Platform not supported");
return Err(BoxliteError::UnsupportedEngine);
}
Err(BoxliteError::Image(msg)) => {
eprintln!("Image error: {}", msg);
// Handle image-specific error
}
Err(e) => {
eprintln!("Unexpected error: {}", e);
return Err(e);
}
}
Ok(())
}BoxLite uses QCOW2 (QEMU Copy-On-Write version 2) for persistent disks.
Location: ~/.boxlite/boxes/{box-id}/disk.qcow2
Format: QCOW2 (copy-on-write image format)
Features:
- Thin provisioning (sparse allocation)
- Copy-on-write snapshots
- Compression support
Size: Specified by disk_size_gb parameter
Lifecycle:
- Created on first box start (if
disk_size_gbset) - Persists across stop/restart
- Deleted when box is removed
Tools:
# Inspect QCOW2 image
qemu-img info ~/.boxlite/boxes/{box-id}/disk.qcow2
# Convert to raw (if needed)
qemu-img convert -f qcow2 -O raw disk.qcow2 disk.rawBoxLite caches OCI images at the layer level for fast starts.
Location: ~/.boxlite/images/
Structure:
~/.boxlite/images/
├── blobs/
│ └── sha256/
│ ├── abc123... # Layer blob
│ ├── def456... # Layer blob
│ └── ...
└── index.json # Image index
Format: OCI Image Layout Specification
Caching:
- Blob-level deduplication across images
- Shared base layers (e.g.,
python:3.11andpython:3.12share layers) - Automatic garbage collection (future feature)
Clearing Cache:
# Clear all cached images
rm -rf ~/.boxlite/images/*
# Images will be re-pulled on next useBox metadata and state are stored as JSON.
Location: ~/.boxlite/boxes/{box-id}/config.json
Format: JSON
Contents:
- Box ID (ULID)
- Image specification
- Resource limits (CPUs, memory)
- Volume mounts
- Port forwarding
- Environment variables
- Creation timestamp
- Status (running, stopped, etc.)
Example:
{
"id": "01JJNH8...",
"image": "python:slim",
"cpus": 2,
"memory_mib": 1024,
"volumes": [
{
"host_path": "/host/data",
"guest_path": "/mnt/data",
"read_only": true
}
],
"ports": [
{
"host_port": 8080,
"guest_port": 80,
"protocol": "tcp"
}
],
"created_at": "2025-01-15T10:30:00Z",
"status": "running"
}Notes:
- Do not manually edit (managed by BoxLite)
- Used for box persistence and recovery
- Deleted when box is removed
BoxLite uses SQLite for metadata persistence.
Locations:
~/.boxlite/db/boxes.db- Box registry and metadata~/.boxlite/db/images.db- Image cache index
Schema:
- Follows Podman-style pattern: immutable config + mutable state
- Box config stored as JSON blob
- Box state tracked separately
- Image layers indexed by digest
Tools:
# Inspect database
sqlite3 ~/.boxlite/db/boxes.db ".tables"
sqlite3 ~/.boxlite/db/boxes.db "SELECT * FROM boxes;"
# Backup
cp ~/.boxlite/db/boxes.db ~/backup/boxes.db.backupNotes:
- Do not manually modify (data corruption risk)
- Backed up automatically on box operations
- Corruption recovery: Delete and recreate from box config files