Experimental — Hotcell is under active development and should not be used in production.

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

VmConfig

VM hardware resources, command, env, networking, and shared directories.

VmResult

Exit code, console output, and optional structured JSON result.

SharedDir

Host-to-guest directory mapping via virtio-fs.

TsiMode

Networking mode: disabled or internet access via TSI.

VmmBackend

Trait for pluggable VMM backends (LibkrunBackend, FirecrackerBackend).

RootfsBuilder

Pulls OCI images and assembles guest root filesystems.

HotcellError

Top-level error type for all library operations.

core imports
// 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

examples/run_vm.rs
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).

networking example
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.

hotcell::config::VmResult
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

01 / INPUT

Input is passed via the HOTCELL_INPUT environment variable, containing a JSON payload for the handler function.

02 / CONSOLE

Anything written to stdout or stderr (print(), console.log(), echo) is captured in VmResult.console_output.

03 / RESULT

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).

api

Handler

handler.py
# 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
deployed_code

Dockerfile

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

execute with VmConfig
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.

javascript

Handler

handler.js
// 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
};
deployed_code

Dockerfile

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"]