Skip to main content
A sandbox is a microVM: a real virtual machine with its own Linux kernel, filesystem, and network stack, running as a child process of whatever application creates it. The security boundary here is hardware virtualization, not Linux namespaces. Container escapes are a well-documented class of vulnerability; breaking out of a microVM requires exploiting the hypervisor itself, which is a fundamentally harder problem.

Creating a sandbox

At minimum, a sandbox needs a name and an image. Everything else has sensible defaults: 1 vCPU, 512 MiB memory, public-only networking, /bin/sh as the shell.
let sb = Sandbox::builder("worker")
    .image("python")
    .create()
    .await?;

Configuration options

let sb = Sandbox::builder("worker")
    .image("python")
    .memory(1024)
    .cpus(2)

    .env("DEBUG", "true")
    .env("API_PORT", "8000")
    .volume("/app/src", |v| v.bind("./src").readonly())
    .volume("/data", |v| v.named("my-data"))
    .volume("/tmp/scratch", |v| v.tmpfs().size(100))
    .create()
    .await?;
OptionDefaultDescription
imageOCI image, local path, or disk image
cpus1Number of virtual CPUs
memory512Guest memory in MiB
workdirDefault working directory for commands
shell/bin/shShell used by shell() calls
env{}Environment variables
volumes[]Volume mounts (bind, named, or tmpfs)
networkpublicNetwork policy and port mappings
scripts{}Named scripts stored at /.msb/scripts/
cpus and memory are limits, not reservations. Setting memory: 512 doesn’t allocate 512 MiB upfront. Physical pages are only allocated as the guest actually touches them, so you can comfortably run many sandboxes on a single host without worrying about overcommitting.

Rootfs sources

microsandbox supports three ways to provide a root filesystem. The choice affects how the filesystem is assembled and what features are available.

OCI images

The most common option. microsandbox pulls the image and stacks its layers as a copy-on-write filesystem. Changes inside the sandbox don’t modify the base image. If two sandboxes use the same image, they share the same cached layers on disk.
Sandbox::builder("worker").image("python").create().await?;

Bind mounts

Use a local directory on the host as the root filesystem directly. The guest sees the directory contents as its /. This is useful for development when you have a pre-built rootfs, or for minimal environments where you’ve assembled the filesystem yourself.
Sandbox::builder("worker").image("./my-rootfs").create().await?;
The guest agent is automatically included in the rootfs during sandbox creation, regardless of rootfs source. You don’t need to add anything to your image or directory.

Disk images

Boot from a QCOW2, Raw, or VMDK disk image. Unlike OCI images (which use a copy-on-write overlay), disk images give the guest raw block device access. See Disk Images for details.
// Auto-detect filesystem
Sandbox::builder("worker")
    .image("./ubuntu-22.04.qcow2")
    .create()
    .await?;

// Explicit filesystem type
Sandbox::builder("worker")
    .image_with(|i| i.disk("./alpine.raw").fstype("ext4"))
    .create()
    .await?;
With OCI images, microsandbox stacks layers and adds a copy-on-write overlay so sandboxes share the base. With disk images, the guest gets the block device directly, so there’s no copy-on-write isolation between sandboxes using the same disk image. Each sandbox needs its own copy (or use QCOW2’s built-in snapshot/backing file support).

Replace existing

Replace a stopped sandbox with the same name instead of failing on conflict. This stops the old sandbox (if still running), removes it, and creates a fresh one.
let sb = Sandbox::builder("worker")
    .image("python")
    .replace()
    .create()
    .await?;

Private registry

Authenticate to private container registries and control when images are pulled.
use microsandbox::{Sandbox, RegistryAuth, PullPolicy};

let sb = Sandbox::builder("worker")
    .image("registry.corp.io/team/app:latest")
    .registry_auth(RegistryAuth::Basic {
        username: "deploy".into(),
        password: std::env::var("REGISTRY_PASSWORD")?,
    })
    .pull_policy(PullPolicy::Always)
    .create()
    .await?;
PolicyBehavior
AlwaysPull the image every time, even if cached locally
IfMissingPull only if the image is not already cached (default)
NeverNever pull; fail if the image is not cached
Once an OCI image is resolved, microsandbox pins the exact layers. A python reference is resolved to an immutable set of layers at first pull. Subsequent start() calls use the pinned layers without re-resolving the mutable tag, so your sandbox is reproducible even if the upstream tag is updated.

Config inspection coming soon

Build a sandbox configuration without booting the VM. Useful for serializing, storing, validating, or modifying configs before creation.
let config = Sandbox::builder("preview")
    .image("python")
    .memory(1024)
    .build()?;

println!("{}", serde_json::to_string_pretty(&config)?);
let sb = Sandbox::create(config).await?;

Customizing the guest environment

If you need to run setup logic, install packages, or inject files before a sandbox starts doing real work, there are two ways to do it without building a custom image:
  • Scripts are files mounted into the sandbox at /.msb/scripts/ and added to PATH. Define them at creation time and call them by name with exec() or shell(). Good for multi-step setup procedures or named entry points.
  • Patches modify the rootfs before the VM boots: write config files, copy directories from the host, create symlinks, append to existing files, etc. The base image stays untouched since patches go into the writable layer.