exec starts it, restarts it and reuses its disk, or fails. Two switches then decide how long it lives: auto_remove and detach.
The states
Two transitions are worth committing to memory:
- Creation is lazy. Constructing a box returns a handle; the microVM comes up on first use. A box that never runs costs nothing but a database row.
- Stop is not remove.
stop()frees the VM but keeps the disk, so restarting reuses everything the box had installed. That is what makes a box a reusable workspace rather than a one-shot container.
box.info().state.status. info() is synchronous — do not await it, and it triggers no VM operation. Field names are exact and easy to get wrong: see BoxInfo / BoxStateInfo.
Walking the states in code
The example below runs standalone: create → exec → stop → remove.auto_remove=False is used to demonstrate the separate stop and remove steps.
Key point:Boxliteis a synchronous context manager (with), whereas aBox’s methods (exec/stop/metrics) are async (requireawait).box.info()is synchronous and must not be awaited.
detach and auto_remove: two independent switches
BoxOptions has two boolean switches that control the lifecycle and do not affect each other:
Common combinations:
auto_remove=Truetogether withdetach=Trueis invalid (a detached Box needs manual lifecycle control); setting both fails validation with an error.
Restarting a stopped Box (rootfs reuse)
For anauto_remove=False Box, the disk persists after stop(). Re-acquire a handle and exec again, and the VM restarts reusing the original rootfs:
Note: data written to tmpfs paths such as/tmpdoes not persist across restarts; only data written to ordinary paths on the rootfs (such as/root) persists.
reattach: getting a second handle in the same process
runtime.get(id_or_name) returns a new handle to an existing Box; the Box may be running or stopped. When get finds nothing it returns None (it does not raise).
Cross-process sharing
One process creates and initializes a Box withdetach=True and exits; the Box keeps running, and another process takes it over with runtime.get(box_id).
Important: do not useThe following is a self-contained script: the parent forks a child that creates a detached Box, and after the child exits the parent takes it over.Boxlite.default()for cross-process scenarios.default()is a process-wide static singleton that keeps holding the runtime lock and does not release it even after a child process exits. Useboxlite.Boxlite(boxlite.Options())instead to construct a releasable runtime instance.
get_info), taken over (get), and restarted (by exec-ing a stopped Box again) across process boundaries.
Bulk shutdown: runtime.shutdown()
runtime.shutdown(timeout=None) gracefully shuts down every Box under that runtime. timeout is in seconds: None = default 10 seconds, -1 = wait indefinitely. After shutdown, any creation-type operation raises RuntimeError.
Parameters and Returns
BoxOptions (lifecycle-related fields)
Note:* Provide one ofnameis not aBoxOptionsfield; it is a separate parameter ofcreate(options, name=...)/get_or_create(options, name=...). Once named, you can operate by name withget(name)/remove(name).
image or rootfs_path; constructing with neither raises an error.
** auto_remove=True and detach=True are mutually exclusive (a detached Box needs manual lifecycle management); setting both fails at validation.
Runtime methods (called on a Boxlite instance)
Box methods (async, require await, except info())
BoxInfo / BoxStateInfo (state access)
box.info() returns a BoxInfo; its state field is a BoxStateInfo, and the state string is at state.status:
Troubleshooting
Awaiting Box.info() as if it were async
info() is a synchronous method.
Calling remove() on a Box / using list()
Removal and listing both happen on the runtime, with the method names remove/list_info:
Removing a running Box errors
remove(id, force=False) requires the Box to be stopped, otherwise it errors. Either stop() first, or use force=True (stop then remove):
Using Boxlite.default() across processes leaves the lock unreleased
default() is a process-wide static singleton that holds the runtime lock until the process ends. Cross-process scenarios (a child creates, the parent takes over) must use boxlite.Boxlite(boxlite.Options()) to construct a releasable instance, otherwise the taking-over process blocks or fails because it cannot acquire the lock.
Current limitation: if the process holding thedefault()lock exits abnormally (crash, kill, or exiting withoutclose()/with), a lock file may remain under$BOXLITE_HOME/locks/, causing the nextBoxlite.default()to reportAnother BoxliteRuntime is already using directory. After confirming no boxlite process is alive (lsof $BOXLITE_HOME/locks/*shows no holder), you can clean it up manually withrm -f $BOXLITE_HOME/locks/*($BOXLITE_HOMEdefaults to~/.boxlite). A runtime that exits normally viawith/close()cleans up after itself.
execution.stdout() yields str, not bytes
The underlying Execution.stdout() / Execution.stderr() async iterators yield already-decoded str chunks, not bytes. Use them directly; calling chunk.decode(...) as if they were bytes raises AttributeError: 'str' object has no attribute 'decode'.
exec exits non-zero without raising
exec reports a non-zero exit through exit_code / exitCode, not by raising. See Error Handling.
No virtualization causes startup failure (environment constraint)
BoxLite requires hardware virtualization:- Linux: requires KVM (
/dev/kvmavailable; under WSL2 the user must be in thekvmgroup); - macOS: uses Apple Hypervisor.framework, no
/dev/kvmrequired (macOS arm64 supported); macOS Intel is not supported.
try/except to inform the user).
Advanced: lifecycle when using SimpleBox
When you use the higher-levelSimpleBox (an async context manager), it lazily creates and starts the Box on async with entry and cleans up on exit according to auto_remove. The lifecycle switches are passed the same way via constructor arguments:
SimpleBox.exec’s timeout parameter istimeout(float) and itsenvis adict; whereas the underlyingBox.exec’s timeout parameter istimeout_secsand itsenvis alist[tuple]. For detach / cross-process / explicit restart, continue to useBoxlite+Boxdirectly, because those operations require access to the Box id and the runtime.

