BoxOptions the SDKs use, and dispatches to a runtime created from a per-invocation home directory. Knowing that shape is what lets you add a subcommand without re-implementing runtime behaviour. Style rules are in the Rust style guide.
- The BoxLite source repository, with the Rust toolchain installed (
rustupdefault stable). Prefer themaketargets over callingcargodirectly. - Familiarity with clap, assert_cmd, and predicates, which the CLI and its tests use.
Building the CLI
From the repository root:make cli depends on runtime:debug, so it builds the debug runtime first (if needed), then runs cargo build -p boxlite-cli. The binary is produced at:
./target/debug/boxlite --help. For a release build:
./target/release/boxlite.
Quick Example — run the CLI integration tests
--no-fail-fast so one failing test does not hide the rest. Pass FILTER=<pattern> to narrow the run:
CLI test layout
The integration tests live undersrc/cli/tests/:
- Entry points:
src/cli/tests/*.rs— roughly one file per command (for examplerun.rs,create.rs,exec.rs,list.rs,rm.rs,pull.rs,images.rs,inspect.rs,info.rs,start.rs,stop.rs,restart.rs,auth.rs,completion.rs), plus a few cross-cutting suites such aslifecycle_journey.rsandregistry.rs. - Shared setup:
src/cli/tests/common/mod.rsprovides aTestContextand the constructor functions that build it.
common::boxlite() returns a TestContext that:
- Resolves the binary under test from the
CARGO_BIN_EXE_boxliteenvironment variable (set by Cargo for the integration test binary). - Allocates a per-test, isolated home directory via
PerTestBoxHome(from theboxlite-test-utilscrate) and passes it with--home. The image/rootfs cache is symlinked in read-only and shared across tests, while the database andboxes/directory are per-test and writable — so tests do not contend over a single global home, and the per-test home is cleaned up automatically on drop. - Applies the test registries (
--registry) so image references resolve against the test mirrors. - Sets a 60-second per-command timeout on the
assert_cmd::Command. - Exposes
cleanup_box(name)/cleanup_boxes(names)helpers andnew_cmd()to issue additional commands against the same home.
common::boxlite_bare() instead when a test needs full control over which registries are used (it skips the default --registry flags).
The first PerTestBoxHome constructed in a test process triggers a one-time warm-up of a shared cache: it pre-pulls the standard test images (alpine:latest, debian:bookworm-slim, python:alpine) under a cross-process lock and warms the guest rootfs pipeline. Subsequent tests reuse that cache, which keeps the suite fast and avoids registry rate limits.
Writing a test
Tests useassert_cmd::Command to invoke the binary and predicates to assert exit codes and stdout/stderr. Always start from common::boxlite() (or boxlite_bare()), and clean up any boxes the test creates — either by passing --rm to the command or by calling ctx.cleanup_box(...).
PerTestBoxHome has a drop guard that fails the test if a box’s shim is left alive when the home is torn down, so a missing cleanup surfaces as a test failure rather than a leaked microVM.
Code structure
- Entry:
src/cli/src/main.rs— parses the CLI and dispatches eachCommandsvariant to the matchingcommands::*::execute(args, &global)(for examplecommands::run::execute). - Subcommands and flags:
src/cli/src/cli.rs— the clap definitions:Cli,Commands,GlobalFlags,ProcessFlags,ResourceFlags,NetworkFlags,PublishFlags,VolumeFlags, andManagementFlags.GlobalFlagscarries--homeand registry flags and exposes helpers for building the runtime and applying flag groups toBoxOptions. - Command implementations:
src/cli/src/commands/*.rs— one module per command (withauth/andserve/as submodule trees). Each exposes anexecute(args, global)and shares runtime-construction helpers fromGlobalFlags.
Adding a new subcommand
- Add a new variant to
Commandsinsrc/cli/src/cli.rs, along with itsArgstype (or reuse existing flag groups such asProcessFlags/ResourceFlags). - Add the new module to
src/cli/src/commands/mod.rsand implementexecute(args, &global)insrc/cli/src/commands/<command>.rs. - In
src/cli/src/main.rs, add acli::Commands::<Variant>(args) => commands::<command>::execute(args, &global).await...arm to the dispatchmatch. - Add tests in
src/cli/tests/<command>.rs(start fromcommon::boxlite()) and runmake test:integration:cli.
Parameters & Returns
Troubleshooting
See also
- Integration tests — how the integration tests run in CI and how to reproduce them locally.
- Rust Style Guide — coding standards for the BoxLite crates.

