[MicroVM] Building Isolated Sandboxes with Firecracker

[MicroVM] Building Isolated Sandboxes with Firecracker

MicroVM technology is gaining attention recently. Traditional containers (like Docker) share the kernel with the host, making them not entirely secure in terms of isolation. On the other hand, MicroVMs like AWS’s Firecracker provide lightweight virtual machines, allowing you to achieve both “container speed” and “VM security.”

In this article, we will practically introduce how to safely execute untrusted code using Firecracker MicroVM, a key consideration in the communication protocol design for ZeroClaw, a Rust-based agent runtime, with accompanying code examples.

Why MicroVM?

In the past development of blog API servers or MCP (Model Context Protocol) clients, container environments were primarily used. However, security becomes even more critical in multi-agent systems or environments that need to execute external code.

  • Single Process Isolation: Each MicroVM has an independent kernel space.
  • Fast Boot: Boot times in milliseconds offer speeds similar to containers.
  • Resource Limits: CPU and memory usage can be strictly controlled.

Prerequisites

To follow this guide, you will need a Linux environment (with KVM enabled) and the Rust toolchain. Firecracker runs on Linux KVM (Kernel-based Virtual Machine).

# Check KVM permissions
ls -la /dev/kvm
# Add to group (if you don't have permissions)
sudo usermod -aG kvm $USER

1. Downloading and Preparing Firecracker

Firecracker is provided as a single binary, making deployment very simple.

cd /tmp
curl -L -o firecracker-v1.9.1 https://github.com/firecracker-microvm/firecracker/releases/download/v1.9.1/firecracker-v1.9.1-x86_64
chmod +x firecracker-v1.9.1
mv firecracker-v1.9.1 firecracker

2. Creating a Root Filesystem for MicroVM Boot

To run a MicroVM, you need a simple Linux kernel and a root filesystem (image). We will download an Ubuntu image for testing.

# Create working directory
mkdir -p fc-demo
cd fc-demo

# Download Ubuntu 22.04 root filesystem
wget https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64-root.tar.xz

# Convert image (raw format)
xz -d ubuntu-22.04-server-cloudimg-amd64-root.tar.xz

3. Writing Control Logic in Rust

Now, let’s use Rust to launch a Firecracker instance, configure networking, and send requests. Passing configurations as JSON using serde_json is the most common approach.

Cargo.toml dependencies:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.12", features = ["blocking"] }

Rust Implementation Code (main.rs):

use std::fs::{self, File};
use std::io::Write;
use std::process::{Command, Stdio};
use serde::Serialize;

// Firecracker configuration structs
#[derive(Serialize)]
struct BootSource {
    kernel_image_path: String,
    boot_args: String,
}

#[derive(Serialize)]
struct Drive {
    drive_id: String,
    path_on_host: String,
    is_root_device: bool,
    is_read_only: bool,
}

#[derive(Serialize)]
struct MachineConfig {
    vcpu_count: u8,
    mem_size_mib: u64,
    ht_enabled: bool,
}

#[derive(Serialize)]
struct NetworkInterface {
    if_id: String,
    guest_mac: String,
    host_dev_name: String,
}

fn main() {
    // 1. Set up working directory
    let vm_id = "my-first-microvm";
    fs::create_dir_all(format!("/tmp/fc-demo/{}", vm_id)).expect("Failed to create vm dir");

    // 2. Run Firecracker process (in background)
    // In a real environment, you would manage it as a daemon or create a socket via API.
    // Here, we assume the API server is run via a socket for simplicity and send configurations.
    
    // Note: A kernel image (vmlinux) is required for actual execution. 
    // It can be downloaded from the AWS Firecracker GitHub releases.
    let kernel_path = "/tmp/fc-demo/vmlinux"; // Path needs verification
    let rootfs_path = "/tmp/fc-demo/ubuntu-22.04-server-cloudimg-amd64-root.tar";

    // (Network configuration is complex in actual tutorials, so TAP device creation is omitted)
    // Example API configuration creation
    let boot_config = BootSource {
        kernel_image_path: kernel_path.to_string(),
        boot_args: "console=ttyS0 reboot=k panic=1 pci=off".to_string(),
    };

    let drive_config = Drive {
        drive_id: "rootfs".to_string(),
        path_on_host: rootfs_path.to_string(),
        is_root_device: true,
        is_read_only: false,
    };

    // Save configurations to JSON files (Firecracker accepts input via HTTP API or JSON files)
    let config_json = serde_json::to_string_pretty(&vec![&boot_config, &drive_config]).unwrap();
    let mut file = File::create(format!("/tmp/fc-demo/{}/vm_config.json", vm_id)).unwrap();
    file.write_all(config_json.as_bytes()).unwrap();

    println!("MicroVM configuration complete: {}", vm_id);
    println!("Firecracker typically requires network setup via TAP and mount configuration.");
    println!("Example CLI usage:");
    println!("./firecracker --api-sock /tmp/fc-demo/{}/api.sock", vm_id);
    println!("Then, send configurations via curl to http://localhost/...");
}

4. Execution and Control (CLI Method)

While automating with Rust code is ideal, it’s recommended to control and verify operations directly via CLI during the development stage.

# 1. Create TAP network interface (for host-guest connection)
sudo ip tuntap add tap0 mode tap
sudo ip addr add 169.254.0.1/30 dev tap0
sudo ip link set tap0 up

# 2. Run Firecracker (API socket mode)
./firecracker --api-sock /tmp/fc-demo/my-api.sock &

# 3. Send boot source configuration
curl --unix-socket /tmp/fc-demo/my-api.sock -i \
     -X PUT 'http://localhost/boot-source' \
     -H 'Accept: application/json' \
     -H 'Content-Type: application/json' \
     -d '{
        "kernel_image_path": "/path/to/vmlinux",
        "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
      }'

# 4. Send drive configuration
curl --unix-socket /tmp/fc-demo/my-api.sock -i \
     -X PUT 'http://localhost/drives/rootfs' \
     -H 'Accept: application/json' \
     -H 'Content-Type: application/json' \
     -d '{
        "drive_id": "rootfs",
        "path_on_host": "/path/to/rootfs.ext4",
        "is_root_device": true,
        "is_read_only": false
      }'
      
# 5. Start the instance
curl --unix-socket /tmp/fc-demo/my-api.sock -i \
     -X PUT 'http://localhost/actions' \
     -H 'Accept: application/json' \
     -H 'Content-Type: application/json' \
     -d '{ "action_type": "InstanceStart" }'

Conclusion and ZeroClaw Application Plan

As we’ve seen, MicroVMs offer strong isolation. In the ZeroClaw project’s “multi-agent architecture,” this technology is highly valuable. By executing each agent’s tasks (e.g., file system access, external network requests) within independent Firecracker instances, we can run “untrusted plugins” without compromising the overall system stability.

In the next post, we will discuss how to optimize communication between agents running on these MicroVMs.

References

  • Firecracker GitHub
  • [MicroVMs: Run isolated sandboxes with full lifecycle control](Hacker News Link)
  • [ZeroClaw Architecture Design Document](Internal Blog Link)
Built with Hugo
Theme Stack designed by Jimmy