Skip to main content

What the C SDK gives you

The C SDK offers two API surfaces for two levels of usage: Key mental model: the Native API is not “call and get a result back”. It is “start an operation -> the operation runs in a background Tokio task -> on completion it posts an event to the runtime queue -> you call boxlite_runtime_drain to dispatch queued events to your callbacks”. If you never call drain, your callbacks never fire. This is the most common source of errors with this SDK, and it is emphasized repeatedly below.
API source of truth: the function that runs a command is boxlite_box_exec (there is no boxlite_execute); boxlite_execution_wait, boxlite_stop_box, and the rest are all callback-style. Treat sdks/c/include/boxlite.h as the single source of truth — every signature on this page matches that header.

Prerequisites

  • The BoxLite C library and headers and a machine with hardware virtualization — see Installation.
  • A C11-compatible compiler: GCC or Clang.
  • Build artifacts (produced by compiling the C crate from the repository root):
    • Shared library: target/release/libboxlite.dylib (macOS) / libboxlite.so (Linux)
    • Static library: target/release/libboxlite.a
    • Header: sdks/c/include/boxlite.h (generated automatically by cbindgen)
Build:
Compile your program (replace the paths with your actual paths):

Quick Example (minimal happy path)

The Simple API manages the runtime itself; commands run synchronously and return buffered stdout/stderr. No drain or callbacks are needed, which makes it the best entry point.

Native API — full callback + post-and-drain skeleton

The following is the standard pattern for the Native API: register callbacks -> start the async operation -> loop on drain until a callback sets a completion flag. This is the core pattern of the SDK.

Runtime image management

Beyond creating boxes, the runtime exposes an image handle for pulling and listing images directly. Obtain the handle synchronously with boxlite_runtime_images; the pull and list operations are callback-style and must be driven by boxlite_runtime_drain like any other async operation.
Note: the image pull/list operations here take a callback and user_data (CBoxImagePullCb / CBoxImageListCb) — they are not synchronous. The result pointers (CImagePullResult* / CImageInfoList*) passed into the callback are valid only during the callback; copy out any fields you need.
Image function signatures and result structs (verified against sdks/c/include/boxlite.h):
CImagePullResult fields: CImageInfoList fields: Each CImageInfo carries reference, repository, tag, id, cached_at (int64_t), size (uint64_t), and has_size (int, whether size is meaningful).

The post-and-drain model in detail

The table below summarizes the complete Native API model: timeout_ms semantics for boxlite_runtime_drain: Return value: the number of dispatched events, or -1 (error, details written to out_error).
Core principle: for every async operation you start with a callback, you must call boxlite_runtime_drain, or the callback never fires and your program hangs waiting for the done flag it set itself. The simplest reliable form is pump_until_done from the Quick Example: while (!done) drain(rt, -1, &err);.
Synchronous vs. asynchronous functions (easy to confuse):

Parameters and returns (core types and signatures)

Every function returns enum BoxliteErrorCode (Ok = 0 is success, unless noted otherwise). On failure, if you pass CBoxliteError *out_error, its code + message are filled in, and message must be freed with boxlite_error_free.

BoxliteErrorCode (complete enum)

The full error-code enum has 22 entries (Ok = 0 through SessionReaped = 21), verified against sdks/c/include/boxlite.h:

BoxliteCommand (exec command descriptor)

ExecResult (Simple API buffered result)

Free the whole struct with boxlite_result_free(result). The Native API does not use this struct — it reads output via streaming callbacks, and the exit code arrives through the int parameter of CExecutionWaitCb.

CBoxInfo (box info, aligned across SDKs)

Free a single struct with boxlite_free_box_info, a list with boxlite_free_box_info_list.

CBoxMetrics / CRuntimeMetrics (metrics fields, real names)

Key CBoxMetrics (per-box) fields: CRuntimeMetrics (runtime-wide): Metrics are returned via callbacks (CBoxMetricsCb / CRuntimeMetricsCb, signature void(*)(struct C...Metrics*, CBoxliteError*, void*)), which also require drain.

Callback function-pointer types

Pointer lifetime in callbacks: pointers passed into a callback (CBoxliteError*, CBoxInfo*, etc.) are valid only while the callback runs; copy anything you want to keep. CBoxHandle* (in the create/get callbacks) transfers ownership to you and must later be freed with boxlite_box_free.

Key function signatures (matching the header)


Configuring a box (volumes / ports / secrets / security)

All options setters are synchronous; once built, pass the options to boxlite_create_box. Options are copied into the box at create time, so you can call boxlite_options_free right after submitting create.

Mounting volumes (the third argument is int read_only, not a string)

Key: the SDK’s third argument is int read_only (1/0 in C), not the CLI-style :ro/:rw string. Passing a string here will not even compile; the CLI -v host:box:ro syntax belongs to the command-line parsing layer and is unrelated to the SDK API.

Port forwarding (host-first convention)

Injecting a secret (vault/secrets entry point)

Security sandbox (toggled through advanced options)

Security is controlled through the advanced layer (consistent with the core model’s BoxOptions.advanced.security); in C this goes through a separate CAdvancedBoxOptions handle:
Security isolation is on by default: not calling set_security_enabled is equivalent to full isolation. Pass 0 to disable it only in environments that genuinely cannot virtualize (debugging).

stdin, signals, and PTY (interactive execution)

The ExecutionHandle returned by boxlite_box_exec supports bidirectional interaction:
Each async operation (signal/resize/kill) needs drain to dispatch its callback after you start it. stdin_write / stdin_close are synchronous and need no drain.

Simple API defaults

In boxlite_simple_new(image, cpus, memory_mib, ...), passing 0 for cpus/memory_mib leaves the value to the engine’s fallback. The Simple API cannot configure volumes/network/security or other advanced options — switch to the Native API when you need those.
Implicit defaults: passing 0 lets the runtime decide, which allocates 1 vCPU / 1024 MiB (vm_defaults in src/boxlite/src/runtime/constants.rs). Pass explicit cpus/memory_mib in production. See Compute resources.

Troubleshooting (common problems and typical errors)

The callback never fires after an async call / the program hangs

Symptom: boxlite_create_box / boxlite_execution_wait returns Ok, but the done flag never becomes 1 and the program hangs. Cause: boxlite_runtime_drain was never called. The Native API is post-and-drain — callbacks are dispatched only when you drain. Fix: after each async operation, loop on drain until the completion flag is set:

boxlite_execute not found / old examples fail to compile with “implicit declaration”

Symptom: reusing older third-party snippets, the compiler reports implicit declaration of function 'boxlite_execute' or argument mismatches on boxlite_execution_wait. Cause: boxlite_execute is not part of the current header. The real exec entry point is boxlite_box_exec(handle, &cmd, &out_execution, &error) (synchronously obtain the handle); the exit code is returned via the int parameter of the callback in boxlite_execution_wait(exec, CExecutionWaitCb, ud, err) (not an int* out-parameter). Fix: follow sdks/c/include/boxlite.h and the pattern in Native API — full callback + post-and-drain skeleton of this page.

The third volume argument was passed as the string "ro" / "rw"

Symptom: boxlite_options_add_bind_mount(opts, host, guest, "ro") fails to compile with a type error (a const char * where an int is expected). Cause: the SDK’s third argument is int read_only, not the CLI’s ro/rw string. Fix: use 1 (read-only) / 0 (read-write): boxlite_options_add_bind_mount(opts, host, guest, 1);.
Compared with other SDKs: the Python/Node volume third element is likewise a bool read_only (e.g. Python (host, guest, True)), and passing a string raises TypeError: 'str' object cannot be cast as 'bool'. Only the CLI -v host:box:ro uses the string syntax.

The command exits non-zero but you receive no error

Symptom: boxlite_simple_run / boxlite_execution_wait returns Ok, but the command actually failed. Cause: a non-zero process exit is not an API error. Ok means “the command was executed successfully”; the command’s own success or failure is the exit code. Fix: always check result->exit_code (Simple) or the int exit_code from the CExecutionWaitCb callback (Native).

dyld: Library not loaded: @rpath/libboxlite.dylib (macOS)

Cause: the loader cannot find the dylib at runtime. Fix:
On Linux the equivalent is export LD_LIBRARY_PATH=<YOUR_BOXLITE_ROOT>/target/release:$LD_LIBRARY_PATH.

Box creation returns Image (code 8) / image pull failed

Cause: a wrong image name or a network failure during pull. Fix:
  1. Confirm the image reference is correct (e.g. "alpine:3.19", "python:slim").
  2. Check network connectivity (the first run pulls the image over the network).
  3. Check disk space: df -h ~/.boxlite.
  4. Enable debug logging: RUST_LOG=debug ./my_program.

Box start returns UnsupportedEngine (code 19) / Engine (code 12) — no virtualization (environment constraint)

This is an environment constraint: BoxLite requires hardware virtualization.
  • Linux: requires KVM.
  • macOS (Apple Silicon ARM64): uses the built-in Hypervisor.framework, no /dev/kvm required, works out of the box.
  • macOS Intel: not supported.
  • Windows: use WSL2 (follow the Linux flow inside it; KVM required).
Without virtualization, start fails (the error is catchable; the process does not crash). For debugging, you can disable the sandbox via advanced options (boxlite_advanced_options_set_security_enabled(adv, 0)), but that sacrifices isolation.

Memory leaks

The C SDK requires manual freeing. Missing any of these leaks memory: All *_free functions are NULL-safe. Diagnose with valgrind (Linux) / leaks (macOS):

Thread safety

  • CBoxliteRuntime is thread-safe (runtime functions may be called concurrently from multiple threads).
  • CBoxHandle / CExecutionHandle / CBoxliteSimple are not thread-safe — do not share the same handle across threads.
  • Callbacks run on the thread that calls boxlite_runtime_drain; do not block inside a callback.

The linker/runtime cannot find the library

Error dyld: Library not loaded: @rpath/libboxlite.dylib (macOS) or error while loading shared libraries: libboxlite.so (Linux). The dynamic linker was not told the library path. Set it:
Or use CMake (examples/c/CMakeLists.txt already sets BUILD_RPATH, so no manual export is needed).