API & Runtimes
Hotcell is a Rust library for running code inside hardware-isolated virtual machines. It supports two pluggable VMM backends (libkrun and Firecracker), real-time streaming, and includes Python and Node.js runtime wrappers for executing handler functions with structured I/O.
Rust Library API
Add Hotcell as a dependency and use the library directly for full control over VM configuration, rootfs assembly, and execution.
Core Types
VM hardware resources, command, env, networking, and shared directories.
Exit code, console output, and optional structured JSON result.
Host-to-guest directory mapping via virtio-fs.
Networking mode: disabled or internet access via TSI.
Trait for pluggable VMM backends (LibkrunBackend, FirecrackerBackend).
Pulls OCI images and assembles guest root filesystems.
Top-level error type for all library operations.
// Core crate: backend-agnostic types, OCI pipeline, registry
use hotcell::config::{VmConfig, VmResult, SharedDir, TsiMode};
use hotcell::backend::VmmBackend;
use hotcell::oci::RootfsBuilder;
use hotcell::ExecutorRegistry;
use hotcell::HotcellError;
// Backend crates (choose one or both)
use hotcell_libkrun::LibkrunBackend;
use hotcell_firecracker::FirecrackerBackend; Run a command in a VM
use std::path::Path;
use hotcell::config::VmConfig;
use hotcell::backend::VmmBackend;
use hotcell::oci::RootfsBuilder;
use hotcell_libkrun::LibkrunBackend;
#[tokio::main]
async fn main() -> Result<(), hotcell::HotcellError> {
// Pull alpine:latest and assemble a rootfs
let builder = RootfsBuilder::open_default()?;
let rootfs = tempfile::tempdir()?;
builder.build("docker.io/library/alpine:latest", rootfs.path(), &[] /* shared_dirs */).await?;
// Configure the VM
let config = VmConfig {
rootfs_path: rootfs.path().to_path_buf(),
command: vec!["/bin/echo".into(), "hello from a VM".into()],
..Default::default()
};
// Create a backend (spawns hotcell-libkrun-worker under the hood)
let worker_bin = Path::new("target/debug/hotcell-libkrun-worker");
let backend = LibkrunBackend::new(worker_bin);
let rootfs_handle = backend.prepare_rootfs(rootfs.path(), &config).await?;
let result = backend.run(&config, &rootfs_handle).await?;
println!("exit: {}", result.exit_code);
println!("output: {}", result.console_output);
// result.result is None because we didn't write /hotcell/result.json
Ok(())
} Run with networking
Set tsi: TsiMode::Inet to enable internet access via TSI (transparent socket impersonation).
use hotcell::config::{TsiMode, VmConfig};
let config = VmConfig {
rootfs_path: rootfs_path.clone(),
command: vec![
"/usr/bin/wget".into(), "-qO-".into(), "http://example.com".into(),
],
tsi: TsiMode::Inet, // enable internet access via TSI
timeout: std::time::Duration::from_secs(15),
..Default::default()
}; VmResult
Every VM execution returns a VmResult containing the exit code, captured console output, and any structured JSON the guest wrote to /hotcell/result.json. The result format is identical regardless of which VMM backend was used.
pub struct VmResult {
pub exit_code: i32, // guest process exit code
pub console_output: String, // raw stdout + stderr (mixed)
pub result: Option<serde_json::Value>, // parsed /hotcell/result.json
} Function Execution
Hotcell includes Python and Node.js runtime wrappers for running handler functions inside VMs. The pattern: a wrapper script loads a user handler module, executes it with JSON input, and writes the return value to /hotcell/result.json.
Structured I/O Flow
Input is passed via the HOTCELL_INPUT environment variable, containing a JSON payload for the handler function.
Anything written to stdout or stderr (print(), console.log(), echo) is captured in VmResult.console_output.
Structured results written to /hotcell/result.json are returned as parsed JSON in VmResult.result.
Python Runtime
The Python wrapper (runtimes/python/wrapper.py) looks for a handle() or main() function in /opt/hotcell/handler.py. Input is read from the HOTCELL_INPUT environment variable (or stdin as a fallback).
Handler
# handler.py
def handle(ctx):
"""ctx is parsed JSON from the HOTCELL_INPUT env var."""
name = ctx.get("name", "world")
print(f"Processing {name}") # goes to VmResult.console_output
return {"greeting": f"Hello, {name}!"} # goes to VmResult.result Dockerfile
FROM python:3.12-slim
RUN mkdir -p /opt/hotcell /hotcell
COPY wrapper.py /opt/hotcell/wrapper.py
COPY handler.py /opt/hotcell/handler.py
WORKDIR /opt/hotcell
ENTRYPOINT ["python3", "/opt/hotcell/wrapper.py"] Run the Python handler
let config = VmConfig {
rootfs_path: rootfs_path.clone(),
command: vec!["python3".into(), "/opt/hotcell/wrapper.py".into()],
env: vec![
("HOTCELL_INPUT".into(), r#"{"name": "hotcell"}"#.into()),
],
memory_mib: 512,
timeout: std::time::Duration::from_secs(60),
..Default::default()
};
let rootfs_handle = backend.prepare_rootfs(&rootfs_path, &config).await?;
let result = backend.run(&config, &rootfs_handle).await?;
// result.result == Some({"greeting": "Hello, hotcell!"})
// result.console_output contains "Processing hotcell" Node.js Runtime
The Node.js wrapper (runtimes/node/wrapper.js) supports both sync and async handler functions. It loads the handler from /opt/hotcell/handler.js and looks for an exported handle or main function.
Handler
// handler.js
exports.handle = async function(ctx) {
const name = ctx.name || "world";
console.log(`Processing ${name}`); // goes to console_output
return { greeting: `Hello, ${name}!` }; // goes to VmResult.result
}; Dockerfile
FROM node:22-slim
RUN mkdir -p /opt/hotcell /hotcell
COPY wrapper.js /opt/hotcell/wrapper.js
COPY handler.js /opt/hotcell/handler.js
WORKDIR /opt/hotcell
ENTRYPOINT ["node", "/opt/hotcell/wrapper.js"]