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 IPv4:
"192.168.1.10" - IPv4 CIDR range:
"10.0.0.0/8"
Create and runtime updates reject any other rule, such as "example.com:443".
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 16 — the bundled guest kernel brings up at most 16 vCPUs, so create rejects larger values (see Resource limits).
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
Size of the box's container disk, a copy-on-write QCOW2 image. The disk is never smaller than the image.
Default: None (the image's size)
Range: 1 to 1024 (1 TB)
Example:
disk_size_gb=None # Image size (default)
disk_size_gb=10 # At least 10 GB
disk_size_gb=100 # At least 100 GBNotes:
- Disk persists across stop/restart only when the box is kept after stop (
auto_delete=0) - Stored at
~/.boxlite/boxes/{box-id}/disks/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(target, options); 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.
Seconds after a successful stop before the box is deleted.
Default: None, which keeps the runtime's default: auto_remove on a local runtime, the
server's policy on a REST runtime.
Example:
auto_delete=0 # Keep the box after stop
auto_delete=3600 # REST runtime: delete an hour after stopNotes:
0: the box persists after stop, andruntime.get(box_id)returns it- Above
0: a local runtime has no sweeper, so it deletes the box at stop - Manual cleanup:
await runtime.remove(box_id)
Deprecated: use auto_delete, which takes precedence when set. True, the default, removes the box
when it stops on a local runtime; REST runtimes do not send it and keep the server's policy.
Base directory for BoxLite runtime data.
Default: ~/.boxlite
Override: Set BOXLITE_HOME environment variable
Structure: see File formats.
Example:
# Custom home directory
runtime = boxlite.Boxlite(boxlite.Options(home_dir="/custom/path"))Proxy for image pulls, with http_proxy, https_proxy, and no_proxy fields. Unset, pulls read the proxy environment variables. Set, it replaces all of them for pulls. Invalid settings fail runtime creation. Available in Rust BoxliteOptions and the CLI --config file.
Default: unset
See Pull proxy for the full behavior.
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:latest