Skip to content

Latest commit

 

History

History
1199 lines (903 loc) · 25.9 KB

File metadata and controls

1199 lines (903 loc) · 25.9 KB

How-to Guides

Lifecycle

Building from Source

Prerequisites

  • Rust 1.75+ (stable)
  • macOS (Apple Silicon) or Linux (x86_64/ARM64) with KVM
  • Python 3.10+ (for Python SDK development)

Quick Start

# 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

Makefile Targets

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 Support

Platform Architecture Hypervisor
macOS ARM64 (Apple Silicon) Hypervisor.framework
Linux x86_64 KVM
Linux ARM64 KVM

Build Scripts

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

Running Examples

BoxLite includes 9 comprehensive Python examples demonstrating all major use cases.

Prerequisites

# Clone repository
git clone https://github.com/boxlite-ai/boxlite.git
cd boxlite

# Build Python SDK
make dev:python

Example Gallery

1. SimpleBox - Foundation Patterns

File: 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.py

Key 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")]
    )

2. CodeBox - AI Code Execution

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.py

Key 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)
""")

3. BrowserBox - Browser Automation

File: examples/python/05_browser_desktop/automate_with_playwright.py

Run:

python examples/python/05_browser_desktop/automate_with_playwright.py

Use Cases:

  • Web scraping
  • E2E testing
  • Browser automation
  • Screenshot generation

4. ComputerBox - Desktop Automation

File: examples/python/05_browser_desktop/automate_desktop.py

Run:

python examples/python/05_browser_desktop/automate_desktop.py

Available Functions:

  • screenshot() - Capture screen
  • left_click(), right_click(), double_click()
  • type_text(text) - Type text
  • get_screen_size() - Get dimensions
  • move_mouse(x, y) - Move cursor
  • And 9 more functions

5. Lifecycle Management

File: examples/python/03_lifecycle/manage_lifecycle.py

Demonstrates box state management.

Run:

python examples/python/03_lifecycle/manage_lifecycle.py

6-9. Other Examples

  • 01_getting_started/list_boxes.py - Runtime introspection
  • 03_lifecycle/share_across_processes.py - Multi-process operations
  • 04_interactive/run_interactive_shell.py - Interactive shells
  • 07_advanced/use_native_api.py - Low-level Rust API

Customizing Examples

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:
    # ...

Configuring Networking

BoxLite provides full internet access and port forwarding through gvproxy.

Network Modes

BoxLite uses gvproxy for NAT networking by default. All boxes can:

  • Access the internet
  • Resolve DNS
  • Make outbound connections

Port Publication

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.

Testing Connectivity

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.1
async 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.

Network Metrics

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}")

Troubleshooting Networking

Problem: Port forward not working

Solutions:

# Check if port is in use
lsof -i :8080

# Stop conflicting process or use different port

Problem: Cannot access internet from box

Solutions:

# Verify gvproxy is running
ps aux | grep gvproxy

# Check DNS resolution
# (run inside box)
nslookup google.com

Volume Mounting

Mount host directories into boxes for data input/output.

Mount Types

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 vs Read-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),
]

Common Use Cases

1. Configuration Files

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)

2. Data Processing

# 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")

3. Source Code Development

# 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/")

4. Persistent Storage with QCOW2

# 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 intact

Performance Considerations

virtiofs 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

Debugging

Enable debug logging and inspect box state for troubleshooting.

Enable Debug Logging

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.py

Rust:

RUST_LOG=debug cargo run

Log Levels:

  • trace - Very verbose, all details
  • debug - Debug information
  • info - Informational messages
  • warn - Warnings
  • error - Errors only

Inspect Box State

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})")

Common Issues & Debug Steps

Issue: Box Fails to Start

Debug Steps:

  1. Check disk space:

    df -h ~/.boxlite
  2. Enable debug logging:

    RUST_LOG=debug python script.py
  3. Verify image exists:

    docker pull <image>
  4. Check hypervisor:

    # Linux
    ls -l /dev/kvm
    grep -E 'vmx|svm' /proc/cpuinfo
    
    # macOS
    sw_vers  # Should be 12+
    uname -m  # Should be arm64

Issue: Command Execution Fails

Debug Steps:

  1. 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}")
  2. Verify command exists:

    result = await box.exec("which", "python3")
    print(result.stdout)  # Should print path
  3. Check working directory:

    result = await box.exec("pwd")
    print(result.stdout)

Issue: Performance Problems

Debug Steps:

  1. 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")
  2. Increase limits:

    boxlite.BoxOptions(
        cpus=4,
        memory_mib=4096,
    )
  3. 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}")

Log Locations

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;

Resource Limits & Tuning

Configure and optimize box resource usage.

CPU Configuration

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")

Memory Management

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_mib if needed

Disk Configuration

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

Scaling Multiple Boxes

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

Using with AI Agents

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.

CodeBox for AI Code Execution

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)

Multiple Tools in One Box

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")

Capturing Output

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
        break

Exit 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}")

Security Considerations

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")

Performance Tips

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 limit

Integration Examples

FastAPI Integration

Expose 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 --reload

Celery Task Queue

Background 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')")

Serverless Function Handler

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())

Deployment Patterns

Production Checklist

Before deploying BoxLite to production:

  • Resource Limits Configured

    • Set appropriate cpus and memory_mib
    • Configure disk_size_gb if persistence needed
    • Test resource consumption under load
  • Error Handling Robust

    • Catch all exceptions
    • Log errors appropriately
    • Implement retry logic if needed
    • Handle timeout scenarios
  • Logging/Monitoring Enabled

    • Configure RUST_LOG for production logging
    • Monitor box metrics
    • Track runtime metrics
    • Set up alerting for failures
  • 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

Docker Container Deployment

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 myapp

Notes:

  • Requires --privileged and --device /dev/kvm
  • Not recommended for multi-tenant environments (security)
  • Consider VM-based deployment instead

Kubernetes Deployment

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: CharDevice

Notes:

  • Requires privileged containers (security consideration)
  • Only works on KVM-enabled nodes
  • Use node selectors to target appropriate nodes

Performance Optimization

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])

Platform-Specific Guides

macOS Sandbox Debugging

For debugging macOS Seatbelt sandbox issues during development, see:

macOS Sandbox Debugging Guide

Covers:

  • Real-time sandbox denial monitoring
  • Log analysis commands
  • SBPL policy syntax
  • Common denial patterns and fixes
  • Iterative debugging workflow