- Rust 1.75+ (stable)
- macOS (Apple Silicon) or Linux (x86_64/ARM64) with KVM
- Python 3.10+ (for Python SDK development)
# Clone the repository
git clone https://github.com/boxlite-ai/boxlite.git
cd boxlite
# Initialize submodules
git submodule update --init --recursive
# Build
make setup
make dev:python| Target | Description |
|---|---|
make setup |
Install platform-specific dependencies |
make guest |
Build guest binary + filesystem tools |
make shim |
Build boxlite-shim binary |
make runtime |
Build complete BoxLite runtime |
make dev:python |
Local Python SDK development |
make dist:python |
Build portable Python wheels |
make clean |
Clean build artifacts |
| Platform | Architecture | Hypervisor |
|---|---|---|
| macOS | ARM64 (Apple Silicon) | Hypervisor.framework |
| Linux | x86_64 | KVM |
| Linux | ARM64 | KVM |
Build scripts are located in scripts/:
scripts/
├── setup/ # Platform-specific setup
│ ├── macos.sh
│ ├── ubuntu.sh
│ ├── manylinux.sh
│ └── musllinux.sh
├── build/ # Build scripts
│ ├── build-guest.sh # Guest binary (cross-compile)
│ ├── build-guest-deps.sh # Static guest e2fsprogs tools
│ ├── build-shim.sh # Shim binary
│ └── build-runtime.sh
├── package/ # Packaging scripts
└── common.sh # Shared utilities
BoxLite includes 9 comprehensive Python examples demonstrating all major use cases.
# Clone repository
git clone https://github.com/boxlite-ai/boxlite.git
cd boxlite
# Build Python SDK
make dev:pythonFile: examples/python/01_getting_started/run_simplebox.py
Demonstrates core BoxLite features:
- Basic command execution
- Stdout/stderr separation
- Environment variables
- Working directory
- Error handling
- Multiple commands in same box
Run:
python examples/python/01_getting_started/run_simplebox.pyKey Patterns:
async with boxlite.SimpleBox(image="python:alpine") as box:
# Execute command
result = await box.exec("ls", "-lh", "/")
print(result.stdout)
# With environment variables
result = await box.exec(
"python", "-c", "import os; print(os.getenv('MY_VAR'))",
env=[("MY_VAR", "value")]
)File: examples/python/01_getting_started/run_codebox.py
Secure Python code execution for AI agents.
Run:
python examples/python/01_getting_started/run_codebox.pyKey Patterns:
async with boxlite.CodeBox() as codebox:
# Install packages automatically
await codebox.install_package("requests")
# Run untrusted code safely
result = await codebox.run("""
import requests
response = requests.get('https://api.github.com/zen')
print(response.text)
""")File: examples/python/05_browser_desktop/automate_with_playwright.py
Run:
python examples/python/05_browser_desktop/automate_with_playwright.pyUse Cases:
- Web scraping
- E2E testing
- Browser automation
- Screenshot generation
File: examples/python/05_browser_desktop/automate_desktop.py
Run:
python examples/python/05_browser_desktop/automate_desktop.pyAvailable Functions:
screenshot()- Capture screenleft_click(),right_click(),double_click()type_text(text)- Type textget_screen_size()- Get dimensionsmove_mouse(x, y)- Move cursor- And 9 more functions
File: examples/python/03_lifecycle/manage_lifecycle.py
Demonstrates box state management.
Run:
python examples/python/03_lifecycle/manage_lifecycle.py01_getting_started/list_boxes.py- Runtime introspection03_lifecycle/share_across_processes.py- Multi-process operations04_interactive/run_interactive_shell.py- Interactive shells07_advanced/use_native_api.py- Low-level Rust API
All examples can be customized by editing the source files:
Change Image:
async with boxlite.SimpleBox(image="ubuntu:22.04") as box:
# ...Add Resources:
async with boxlite.SimpleBox(
image="python:slim",
cpus=2,
memory_mib=2048
) as box:
# ...Mount Volumes:
async with boxlite.SimpleBox(
image="python:slim",
volumes=[("/host/data", "/mnt/data", True)]
) as box:
# ...BoxLite provides full internet access and port forwarding through gvproxy.
BoxLite uses gvproxy for NAT networking by default. All boxes can:
- Access the internet
- Resolve DNS
- Make outbound connections
Explicitly publish guest ports when ordinary host applications need a local TCP listener. The local runtime owns the listener for the lifetime of the running box and accepts repeated connections.
Basic Port Publication:
import boxlite
options = boxlite.BoxOptions(
image="python:slim",
ports=[
(8080, 80, "tcp"), # Host 8080 → Guest 80 (HTTP)
(8443, 443, "tcp"), # Host 8443 → Guest 443 (HTTPS)
]
)
runtime = boxlite.Boxlite.default()
box = runtime.create(options)Multiple Ports:
ports=[
(8080, 80, "tcp"), # HTTP
(8443, 443, "tcp"), # HTTPS
(5432, 5432, "tcp"), # PostgreSQL
(6379, 6379, "tcp"), # Redis
{"guest_port": 3000}, # Automatic host port
]Custom Port Mapping:
# Map host port 3000 to guest port 8000
ports=[(3000, 8000, "tcp")]Port publication is available only with the local runtime and supports TCP. It is appropriate for browsers, database clients, and other programs that expect a normal host address.
For SDK code that must work with local and remote runtimes, use
box.network.tunnel(port) and open byte streams with connect(). A tunnel
can be consumed by connect() or by forward() to bind a local listener.
Remote CLI users can run boxlite network tunnel BOX PORT to obtain the public
service URL.
Image EXPOSE declarations are metadata only and never create host listeners.
From Host to Box:
import asyncio
import boxlite
import requests
async def test_connectivity():
async with boxlite.SimpleBox(
image="python:slim",
ports=[(8080, 8000, "tcp")]
) as box:
# Start web server in box
await box.exec("python", "-m", "http.server", "8000", background=True)
# Test from host
response = requests.get("http://localhost:8080")
print(f"Status: {response.status_code}")
asyncio.run(test_connectivity())From Box to Internet:
async with boxlite.SimpleBox(image="alpine:latest") as box:
# Test DNS
result = await box.exec("nslookup", "google.com")
print(result.stdout)
# Test HTTP
result = await box.exec("wget", "-O-", "https://api.github.com/zen")
print(result.stdout)From Box to Host Loopback:
# On the host, start a service bound to loopback
python3 -m http.server 8081 --bind 127.0.0.1async with boxlite.SimpleBox(image="alpine:latest") as box:
result = await box.exec(
"wget",
"-O-",
"http://host.boxlite.internal:8081",
)
print(result.stdout)host.boxlite.internal is a built-in BoxLite hostname that resolves to the
host loopback proxy address. It is not a Docker compatibility alias.
Security note: with an empty allow_net, any service bound to host loopback is
reachable from inside the box. A non-empty allow_net must list
"192.168.127.254", or a CIDR covering it, for the alias to be reachable.
Monitor network usage:
box = await runtime.create(boxlite.BoxOptions(image="alpine"))
metrics = await box.metrics()
print(f"Bytes sent: {metrics.network_bytes_sent}")
print(f"Bytes received: {metrics.network_bytes_received}")Problem: Port forward not working
Solutions:
# Check if port is in use
lsof -i :8080
# Stop conflicting process or use different portProblem: Cannot access internet from box
Solutions:
# Verify gvproxy is running
ps aux | grep gvproxy
# Check DNS resolution
# (run inside box)
nslookup google.comMount host directories into boxes for data input/output.
virtiofs (Default):
- High-performance file sharing
- Low overhead
- Real-time host-guest synchronization
QCOW2 (Persistent Disk):
- Block device
- Survives box restarts
- Copy-on-write
Read-Only Mount (Data Input):
volumes=[
("/host/config", "/etc/app/config", True),
("/host/datasets", "/mnt/data", True),
]Read-Write Mount (Data Output):
volumes=[
("/host/output", "/mnt/output", False),
("/host/logs", "/var/log/app", False),
]import os
import boxlite
# Mount config directory
async with boxlite.SimpleBox(
image="python:slim",
volumes=[
(os.path.expanduser("~/.config/myapp"), "/etc/myapp", True)
]
) as box:
result = await box.exec("cat", "/etc/myapp/config.yaml")
print(result.stdout)# Input data (read-only), output results (read-write)
async with boxlite.SimpleBox(
image="python:slim",
volumes=[
("/data/input", "/mnt/input", True),
("/data/output", "/mnt/output", False),
]
) as box:
await box.exec("python", "process.py", "--input", "/mnt/input", "--output", "/mnt/output")# Mount source code for live development
async with boxlite.SimpleBox(
image="python:slim",
volumes=[
(os.getcwd(), "/workspace", False)
],
working_dir="/workspace"
) as box:
# Run tests in isolated environment
await box.exec("pytest", "tests/")# Create box with persistent disk
box = runtime.create(boxlite.BoxOptions(
image="postgres:latest",
disk_size_gb=20, # 20 GB persistent disk
env=[("POSTGRES_PASSWORD", "secret")],
))
# Data survives stop/restart
await box.stop()
# ... later ...
box = runtime.get(box.id) # Disk still intactvirtiofs Performance:
- Fast for small files
- Slight overhead for large files
- Real-time synchronization
QCOW2 Performance:
- Block-level access (faster for large files)
- Copy-on-write overhead
- No real-time sync with host
Best Practices:
- Use read-only mounts when possible (lower overhead)
- Mount specific directories, not entire filesystem
- For large datasets, consider QCOW2 disk
Enable debug logging and inspect box state for troubleshooting.
Python:
# 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.pyRust:
RUST_LOG=debug cargo runLog Levels:
trace- Very verbose, all detailsdebug- Debug informationinfo- Informational messageswarn- Warningserror- Errors only
Get Box Information:
box = await runtime.create(boxlite.BoxOptions(image="alpine"))
info = await box.info()
print(f"ID: {info.id}")
print(f"Status: {info.state.status}")
print(f"Image: {info.image}")
print(f"CPUs: {info.cpus}")
print(f"Memory: {info.memory_mib} MiB")
print(f"Created: {info.created_at}")Get Box Metrics:
metrics = await box.metrics()
print(f"CPU time: {metrics.cpu_time_ms}ms")
print(f"Memory usage: {metrics.memory_usage_bytes / (1024**2):.2f} MB")
print(f"Network sent: {metrics.network_bytes_sent}")
print(f"Network received: {metrics.network_bytes_received}")List All Boxes:
boxes = await runtime.list_info()
for info in boxes:
print(f"{info.id}: {info.state.status} ({info.image})")Debug Steps:
-
Check disk space:
df -h ~/.boxlite -
Enable debug logging:
RUST_LOG=debug python script.py
-
Verify image exists:
docker pull <image>
-
Check hypervisor:
# Linux ls -l /dev/kvm grep -E 'vmx|svm' /proc/cpuinfo # macOS sw_vers # Should be 12+ uname -m # Should be arm64
Debug Steps:
-
Check exit code:
result = await box.exec("command") if result.exit_code != 0: print(f"Exit code: {result.exit_code}") print(f"Stderr: {result.stderr}")
-
Verify command exists:
result = await box.exec("which", "python3") print(result.stdout) # Should print path
-
Check working directory:
result = await box.exec("pwd") print(result.stdout)
Debug Steps:
-
Check resource usage:
metrics = await box.metrics() print(f"Memory: {metrics.memory_usage_bytes / (1024**2):.2f} MB") print(f"CPU time: {metrics.cpu_time_ms}ms")
-
Increase limits:
boxlite.BoxOptions( cpus=4, memory_mib=4096, )
-
Monitor runtime metrics:
runtime_metrics = await runtime.metrics() print(f"Active boxes: {runtime_metrics.active_boxes}") print(f"Total exec calls: {runtime_metrics.total_exec_calls}")
Runtime Logs:
- Location:
~/.boxlite/logs/ - Enable with:
RUST_LOG=debug
Guest Logs:
- Inside box:
/var/log/ - Requires persistent disk to access after box stops
Database:
~/.boxlite/db/boxes.db~/.boxlite/db/images.db
Inspect Database:
sqlite3 ~/.boxlite/db/boxes.db
.tables
SELECT * FROM boxes;Configure and optimize box resource usage.
Set CPU Count:
boxlite.BoxOptions(
cpus=2, # 2 CPU cores
)Range: 1 to host CPU count
Behavior:
- Proportional scheduling (shares-based)
- Does not reserve physical cores
- Multiple boxes can exceed host CPU count
Monitor Usage:
metrics = await box.metrics()
print(f"CPU time: {metrics.cpu_time_ms}ms")Set Memory Limit:
boxlite.BoxOptions(
memory_mib=1024, # 1 GB
)Range: 128 to 65536 MiB (64 GiB)
Default: 512 MiB
Behavior:
- Hard limit (box killed if exceeded)
- Minimum 128 MiB required
Monitor Usage:
metrics = await box.metrics()
memory_mb = metrics.memory_usage_bytes / (1024**2)
print(f"Memory: {memory_mb:.2f} MB")Out of Memory:
- Box process is killed
- Check stderr for OOM messages
- Increase
memory_mibif needed
Ephemeral (Default):
boxlite.BoxOptions(
disk_size_gb=None # No persistent disk
)Persistent:
boxlite.BoxOptions(
disk_size_gb=20 # 20 GB persistent disk
)Performance:
- Ephemeral: Fastest (in-memory/tmpfs)
- QCOW2: Moderate (copy-on-write overhead)
I/O Monitoring:
- Currently not exposed in metrics
- Future feature
Resource Pooling:
import asyncio
import boxlite
async def run_box(box_id):
async with boxlite.SimpleBox(
image="python:slim",
cpus=1,
memory_mib=512,
) as box:
result = await box.exec("python", "-c", f"print('Box {box_id}')")
return result.stdout
async def main():
# Run 10 boxes concurrently
tasks = [run_box(i) for i in range(10)]
results = await asyncio.gather(*tasks)
for i, result in enumerate(results):
print(f"Box {i}: {result}")
asyncio.run(main())Concurrency Limits:
- Limited by host resources (CPU, memory)
- Each box: minimum 128 MiB + overhead
- Monitor with
(await runtime.metrics()).active_boxes
Best Practices:
- Use asyncio for concurrent execution
- Configure appropriate resource limits
- Monitor metrics to avoid oversubscription
For a comprehensive guide covering configuration, concurrency, timeouts, security, and file transfer patterns, see AI Agent Integration Guide.
BoxLite is designed for AI agents that need full execution freedom.
Use Case: AI generates Python code that needs execution.
Example:
import asyncio
import boxlite
async def execute_ai_code(code: str):
"""Execute untrusted AI-generated code safely."""
async with boxlite.CodeBox() as codebox:
try:
result = await codebox.run(code)
return {"success": True, "output": result}
except Exception as e:
return {"success": False, "error": str(e)}
# AI-generated code
ai_code = """
import requests
response = requests.get('https://api.github.com/repos/python/cpython')
data = response.json()
print(f"Stars: {data['stargazers_count']}")
"""
result = asyncio.run(execute_ai_code(ai_code))
print(result)AI agents often need multiple tools. BoxLite provides a full Linux environment.
Example:
async with boxlite.SimpleBox(image="python:slim") as box:
# File system access
await box.exec("mkdir", "-p", "/workspace")
# Python code execution
await box.exec("python", "-c", "print('Hello')")
# Package installation
await box.exec("pip", "install", "requests")
# Network requests
await box.exec("curl", "https://api.github.com/zen")
# File manipulation
await box.exec("echo", "data", ">", "/workspace/file.txt")Streaming Output:
execution = await box.exec("python", "long_running_script.py")
# Stream stdout in real-time
stdout = execution.stdout()
async for line in stdout:
print(f"AI Output: {line}")
# Parse and react to output
if "ERROR" in line:
await execution.kill() # Stop on error
breakExit Codes:
result = await box.exec("command")
if result.exit_code == 0:
print("Success!")
else:
print(f"Failed with code {result.exit_code}")
print(f"Error: {result.stderr}")Isolation:
- Hardware-level VM isolation (not just containers)
- AI cannot escape to host system
- Network access can be controlled
Resource Limits:
# Prevent AI from consuming all resources
boxlite.BoxOptions(
cpus=2, # Limit CPUs
memory_mib=1024, # Limit memory
disk_size_gb=10, # Limit disk
# No port forwarding = no incoming connections
)Timeout Handling:
import asyncio
async def execute_with_timeout(box, command, timeout=30):
"""Execute with timeout to prevent infinite loops."""
try:
execution = await box.exec(*command)
result = await asyncio.wait_for(
execution.wait(),
timeout=timeout
)
return result
except asyncio.TimeoutError:
await execution.kill()
raise TimeoutError(f"Command exceeded {timeout}s timeout")Reuse Boxes:
# Create once, use many times
box = runtime.create(boxlite.BoxOptions(image="python:slim"))
for code in ai_generated_codes:
result = await box.exec("python", "-c", code)
# Process result
# Cleanup when done
await box.remove()Batch Operations:
# Execute multiple commands in one box (faster than creating new boxes)
async with boxlite.SimpleBox(image="python:slim") as box:
await box.exec("pip", "install", "requests")
result1 = await box.exec("python", "script1.py")
result2 = await box.exec("python", "script2.py")
result3 = await box.exec("python", "script3.py")Monitor Resources:
metrics = await box.metrics()
if metrics.memory_usage_bytes > 0.8 * (1024**3): # 80% of 1GB
print("Warning: High memory usage")
# Consider recreating box or increasing limitExpose BoxLite as a REST API:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import boxlite
import asyncio
app = FastAPI()
class CodeRequest(BaseModel):
code: str
timeout: int = 30
@app.post("/execute")
async def execute_code(request: CodeRequest):
"""Execute Python code in isolated box."""
try:
async with boxlite.CodeBox() as codebox:
result = await asyncio.wait_for(
codebox.run(request.code),
timeout=request.timeout
)
return {"output": result}
except asyncio.TimeoutError:
raise HTTPException(status_code=408, detail="Execution timeout")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Run: uvicorn main:app --reloadBackground task processing with BoxLite:
from celery import Celery
import boxlite
import asyncio
app = Celery('tasks', broker='redis://localhost:6379')
@app.task
def run_code_task(code: str):
"""Run code in box as background task."""
async def execute():
async with boxlite.CodeBox() as codebox:
return await codebox.run(code)
return asyncio.run(execute())
# Usage: run_code_task.delay("print('Hello')")AWS Lambda / Cloud Functions integration:
import boxlite
import asyncio
def handler(event, context):
"""Serverless function handler."""
code = event.get('code', '')
async def execute():
async with boxlite.SimpleBox(image="python:slim") as box:
result = await box.exec("python", "-c", code)
return {
'statusCode': 200,
'body': result.stdout
}
return asyncio.run(execute())Before deploying BoxLite to production:
-
Resource Limits Configured
- Set appropriate
cpusandmemory_mib - Configure
disk_size_gbif persistence needed - Test resource consumption under load
- Set appropriate
-
Error Handling Robust
- Catch all exceptions
- Log errors appropriately
- Implement retry logic if needed
- Handle timeout scenarios
-
Logging/Monitoring Enabled
- Configure
RUST_LOGfor production logging - Monitor box metrics
- Track runtime metrics
- Set up alerting for failures
- Configure
-
Performance Tested
- Load test with expected concurrency
- Measure box startup time
- Test resource limits under stress
- Verify cleanup happens correctly
-
Security Review
- Verify network isolation configured correctly
- Check resource limits prevent DoS
- Review error messages (no sensitive data leaked)
- Audit code execution paths
Run BoxLite inside Docker (requires privileged mode for KVM):
FROM ubuntu:22.04
# Install dependencies
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# Install BoxLite
RUN pip3 install boxlite
# Copy application
COPY app.py /app/app.py
WORKDIR /app
CMD ["python3", "app.py"]Run with KVM access:
docker run --privileged --device /dev/kvm:/dev/kvm myappNotes:
- Requires
--privilegedand--device /dev/kvm - Not recommended for multi-tenant environments (security)
- Consider VM-based deployment instead
Deploy BoxLite on Kubernetes:
apiVersion: v1
kind: Pod
metadata:
name: boxlite-app
spec:
containers:
- name: app
image: myapp:latest
securityContext:
privileged: true # Required for KVM
volumeMounts:
- name: dev-kvm
mountPath: /dev/kvm
resources:
limits:
memory: "4Gi"
cpu: "2"
volumes:
- name: dev-kvm
hostPath:
path: /dev/kvm
type: CharDeviceNotes:
- Requires privileged containers (security consideration)
- Only works on KVM-enabled nodes
- Use node selectors to target appropriate nodes
Box Reuse:
# Create pool of boxes
boxes = [
runtime.create(boxlite.BoxOptions(image="python:slim"))
for _ in range(10)
]
# Reuse boxes for multiple tasks
for i, task in enumerate(tasks):
box = boxes[i % len(boxes)]
await box.exec("python", "-c", task.code)Image Caching:
# Pre-pull images before high traffic
images = ["python:slim", "node:alpine", "alpine:latest"]
for image in images:
runtime.create(boxlite.BoxOptions(image=image))
# Images are now cached in ~/.boxlite/images/Concurrent Execution:
import asyncio
async def run_tasks_concurrently(tasks):
"""Run multiple tasks in parallel."""
async def run_task(task):
async with boxlite.SimpleBox(image="python:slim") as box:
return await box.exec("python", "-c", task.code)
return await asyncio.gather(*[run_task(t) for t in tasks])For debugging macOS Seatbelt sandbox issues during development, see:
Covers:
- Real-time sandbox denial monitoring
- Log analysis commands
- SBPL policy syntax
- Common denial patterns and fixes
- Iterative debugging workflow