Skip to content

Core Concepts

This guide covers the fundamental concepts behind Capsa's design and how its components work together.

Architecture Overview

Capsa provides a unified Rust API that abstracts platform-specific hypervisor implementations behind a clean interface:

+-----------------------------------------------------------+
|                       User Code                           |
+-----------------------------------------------------------+
                            |
                            v
+-----------------------------------------------------------+
|                    capsa (public API)                     |
|  Capsa, VmBuilder, VmHandle, VmConsole, VsockSocket       |
+-----------------------------------------------------------+
                            |
                            v
+-----------------------------------------------------------+
|              HypervisorBackend trait (internal)           |
+-----------------------------------------------------------+
            |                               |
            v                               v
+------------------------+    +-----------------------------+
|   Linux KVM Backend    |    |      macOS Backend          |
|   (capsa-linux-kvm)    |    |   (subprocess via vzd)      |
+------------------------+    +-----------------------------+

The abstraction layer allows your code to work unchanged across platforms. You write against the capsa API, and Capsa automatically selects the appropriate backend based on the platform and configuration.

VM Lifecycle

A VM progresses through a well-defined lifecycle from creation to termination.

Creating a VM

All VM creation starts with Capsa::vm(), which accepts a boot configuration and returns a builder:

rust
use capsa::{Capsa, LinuxDirectBootConfig};

let config = LinuxDirectBootConfig::new("./kernel", "./initrd")
    .with_root_disk("./rootfs.raw");

let builder = Capsa::vm(config);

Building and Starting

Calling .build().await validates the configuration, starts the VM, and returns a handle:

rust
let vm = Capsa::vm(config)
    .cpus(2)
    .memory_mb(1024)
    .console_enabled()
    .build()
    .await?;

The build step validates that:

  • The backend supports all requested features
  • Resource limits are within bounds
  • Referenced files (kernel, disk images) exist and are accessible

VM States

A VM transitions through these states:

StateDescription
CreatedVM is configured but not yet started
StartingVM is in the process of booting
RunningVM is running and accessible
StoppingGraceful shutdown in progress
StoppedVM has terminated (includes exit code)
FailedVM encountered an error (includes message)

Check the current state with vm.status():

rust
match vm.status() {
    VmStatus::Running => println!("VM is running"),
    VmStatus::Stopped { exit_code } => println!("Exited with: {:?}", exit_code),
    VmStatus::Failed { message } => eprintln!("Error: {}", message),
    _ => {}
}

Stopping a VM

There are two ways to stop a VM:

Graceful shutdown with stop():

rust
vm.stop().await?;

This sends a shutdown request to the guest and waits up to 30 seconds. If the guest does not respond, it falls back to a force kill. Use stop_with_timeout() for a custom grace period.

Immediate termination with kill():

rust
vm.kill().await?;

This forcefully terminates the VM without waiting for the guest to shut down cleanly.

Waiting for Exit

Use wait() to block until the VM exits:

rust
let status = vm.wait().await?;

Or with a timeout:

rust
if let Some(status) = vm.wait_timeout(Duration::from_secs(60)).await? {
    println!("VM exited: {:?}", status);
} else {
    println!("Timeout waiting for VM");
}

Boot Methods

Capsa supports two boot methods, each suited for different use cases.

Linux Direct Boot

Boots the kernel directly without a bootloader. This is the fastest option for Linux-only workloads.

rust
use capsa::{Capsa, LinuxDirectBootConfig};

let config = LinuxDirectBootConfig::new("./vmlinuz", "./initrd.img")
    .with_root_disk("./rootfs.raw");

let vm = Capsa::vm(config).build().await?;

Advantages:

  • Fastest boot time (no BIOS/UEFI, no bootloader)
  • Simple configuration (just kernel and initrd)
  • Ideal for integration testing and ephemeral workloads

When to use:

  • Linux-only environments
  • Test VMs where boot speed matters
  • Minimal/custom Linux systems

UEFI Boot

Boots from a disk containing an EFI bootloader. This is OS-agnostic and supports any operating system with a UEFI-compatible bootloader.

rust
use capsa::{Capsa, UefiBootConfig};

let config = UefiBootConfig::new("./disk.raw")
    .with_efi_variable_store("./nvram.efivarstore");

let vm = Capsa::vm(config).build().await?;

Advantages:

  • OS-agnostic (Linux, Windows, BSDs, etc.)
  • Supports standard disk images
  • EFI variable persistence across reboots

When to use:

  • Non-Linux operating systems
  • Standard disk images with existing bootloaders
  • When you need EFI variable persistence

Builder Pattern

Capsa uses the builder pattern for fluent, readable VM configuration. Methods chain together and apply configuration incrementally.

Common Configuration Methods

rust
let vm = Capsa::vm(config)
    // Resource allocation
    .cpus(4)
    .memory_mb(2048)

    // Console access
    .console_enabled()

    // Networking
    .network(NetworkMode::Nat)

    // Directory sharing
    .share("./workspace", "/mnt/workspace", MountMode::ReadWrite)

    // Host-guest communication
    .vsock_listen(1024)

    // Additional disks
    .disk(DiskImage::new("./data.raw"))

    .build()
    .await?;

Type Safety via Generics

The builder uses generic type parameters to enforce compile-time constraints:

rust
VmBuilder<LinuxDirectBootConfig, No>  // Single VM builder
VmBuilder<LinuxDirectBootConfig, Yes> // Pool builder

This ensures that pool-specific methods are only available on pool builders, and vice versa.

Handle Types

After building a VM, you interact with it through various handle types.

VmHandle

The primary interface for controlling a running VM:

rust
let vm: VmHandle = Capsa::vm(config).build().await?;

// Check status
let status = vm.status();

// Access console
let console = vm.console().await?;

// Access vsock
if let Some(socket) = vm.vsock_socket(1024) {
    let stream = socket.connect().await?;
}

// Lifecycle control
vm.stop().await?;

VmConsole

Provides serial console interaction for automation:

rust
let console = vm.console().await?;

// Wait for patterns
console.wait_for("login:").await?;
console.write_line("root").await?;

// Run commands
let output = console.exec("ls /", Duration::from_secs(5)).await?;

// Login helper
console.login("user", Some("password")).await?;

INFO

Console access requires .console_enabled() on the builder.

VsockSocket

Enables host-guest communication via VM sockets:

rust
// Configure during build
let vm = Capsa::vm(config)
    .vsock_listen(1024)
    .build()
    .await?;

// Connect from host
if let Some(socket) = vm.vsock_socket(1024) {
    let mut stream = socket.connect().await?;
    stream.write_all(b"hello guest").await?;
}

PooledVm

A VM obtained from a pool that auto-releases on drop:

rust
let pool = Capsa::pool(config)
    .cpus(2)
    .memory_mb(512)
    .build(5)
    .await?;

// Reserve a VM
let vm: PooledVm = pool.reserve().await?;

// Use it like a regular VmHandle
let console = vm.console().await?;

// VM is killed and replaced when `vm` goes out of scope

PooledVm implements Deref<Target = VmHandle>, so all VmHandle methods work directly.

Backend Selection

Capsa automatically selects the appropriate backend based on platform and feature flags.

Platform-Specific Backends

PlatformBackendRequirements
LinuxKVM/dev/kvm access
macOSVirtualization.frameworkApple Silicon or Intel Mac

Automatic Selection

When you call .build(), Capsa:

  1. Queries available backends via available_backends()
  2. Checks each backend's availability (e.g., /dev/kvm exists)
  3. Selects the first available backend
  4. Validates your configuration against the backend's capabilities

Capability Checking

You can query backend capabilities programmatically:

rust
use capsa::capabilities::{available_backends, BackendCapabilities};

for backend in available_backends() {
    if !backend.is_available() {
        continue;
    }

    let caps = backend.capabilities();
    println!("Backend: {}", backend.name());
    println!("  Linux direct boot: {}", caps.boot_methods.linux_direct);
    println!("  UEFI boot: {}", caps.boot_methods.uefi);
    println!("  VirtioFS: {}", caps.share_mechanisms.virtio_fs);
    println!("  Vsock: {}", caps.devices.vsock);
}

This is useful for:

  • Skipping tests when a feature is unavailable
  • Choosing configuration options dynamically
  • Debugging why a build fails

Feature Flags

Backend availability is controlled by Cargo features:

FeatureDescription
linux-kvmLinux KVM backend
macos-subprocessmacOS Virtualization.framework

Enable the appropriate feature for your target platform:

toml
[dependencies]
capsa = { version = "...", features = ["linux-kvm"] }

Released under the MIT License.