docs/sandboxes/overview.mdx
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.
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.
await using sb = await Sandbox.builder("worker")
.image("python")
.create();
sb = await Sandbox.create("worker", image="python")
msb create python --name worker
import { Sandbox } from "microsandbox";
await using sb = await Sandbox.builder("worker")
.image("python")
.memory(1024)
.cpus(2)
.env("DEBUG", "true")
.env("API_PORT", "8000")
.volume("/app/src", (m) => m.bind("./src").readonly())
.volume("/data", (m) => m.named("my-data"))
.volume("/tmp/scratch", (m) => m.tmpfs().size(100))
.create();
from microsandbox import Sandbox, Volume
sb = await Sandbox.create(
"worker",
image="python",
memory=1024,
cpus=2,
env={"DEBUG": "true", "API_PORT": "8000"},
volumes={
"/app/src": Volume.bind("./src", readonly=True),
"/data": Volume.named("my-data"),
"/tmp/scratch": Volume.tmpfs(size_mib=100),
},
)
msb create python --name worker \
-c 2 \
-m 1G \
-e DEBUG=true \
-e API_PORT=8000 \
-v ./src:/app/src:ro \
-v my-data:/data \
--tmpfs /tmp/scratch:100
| Option | Default | Description |
|---|---|---|
image | — | OCI image, local path, or disk image |
cpus | 1 | Number of virtual CPUs |
memory | 512 | Guest memory in MiB |
workdir | — | Default working directory for commands |
shell | /bin/sh | Shell used by shell() calls |
env | {} | Environment variables |
volumes | [] | Volume mounts (bind, named, or tmpfs) |
network | public | Network policy and port mappings |
scripts | {} | Named scripts stored at /.msb/scripts/ |
microsandbox supports three ways to provide a root filesystem. The choice affects how the filesystem is assembled and what features are available.
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.
<CodeGroup> ```rust Rust Sandbox::builder("worker").image("python").create().await?; ```await Sandbox.builder("worker").image("python").create();
await Sandbox.create("worker", image="python")
msb create python --name worker
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.
await Sandbox.builder("worker").image("./my-rootfs").create();
await Sandbox.create("worker", image="./my-rootfs")
msb create ./my-rootfs --name worker
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.
<CodeGroup> ```rust Rust // 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?;
```typescript TypeScript
// Auto-detect the disk image format from the file extension.
await Sandbox.builder("worker")
.image("./alpine.qcow2")
.create();
from microsandbox import Image, Sandbox
# Auto-detect filesystem
await Sandbox.create("worker", image="./ubuntu-22.04.qcow2")
# Explicit filesystem type
await Sandbox.create("worker", image=Image.disk("./alpine.raw", fstype="ext4"))
msb create ./alpine.qcow2 --name worker
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.
<CodeGroup> ```rust Rust let sb = Sandbox::builder("worker") .image("python") .replace() .create() .await?; ```await using sb = await Sandbox.builder("worker")
.image("python")
.replace()
.create();
sb = await Sandbox.create("worker", image="python", replace=True)
msb create --replace python --name worker
Authenticate to private container registries and control when images are pulled.
<CodeGroup> ```rust Rust 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?;
```typescript TypeScript
import { Sandbox } from "microsandbox";
await using sb = await Sandbox.builder("worker")
.image("private.registry.io/org/app:v1")
.registry((r) => r.auth({
kind: "basic",
username: "deploy",
password: process.env.REGISTRY_TOKEN!,
}))
.pullPolicy("always")
.create();
import os
from microsandbox import PullPolicy, RegistryAuth, Sandbox
sb = await Sandbox.create(
"worker",
image="private.registry.io/org/app:v1",
registry_auth=RegistryAuth.basic("deploy", os.environ["REGISTRY_PASSWORD"]),
pull_policy=PullPolicy.ALWAYS,
)
msb registry login private.registry.io -u deploy
msb create private.registry.io/org/app:v1 \
--name worker --pull always
| Policy | Behavior |
|---|---|
"always" | Pull the image every time, even if cached locally |
"if-missing" | Pull only if the image is not already cached (default) |
"never" | Never pull; fail if the image is not cached |
Build a sandbox configuration without booting the VM. Useful for serializing, storing, validating, or modifying configs before creation.
<CodeGroup> ```rust Rust let config = Sandbox::builder("preview") .image("python") .memory(1024) .build()?;println!("{}", serde_json::to_string_pretty(&config)?); let sb = Sandbox::create(config).await?;
```typescript TypeScript
const builder = Sandbox.builder("preview").image("python").memory(1024);
const config = builder.build();
console.log(JSON.stringify(config, null, 2));
const sb = await builder.create();
import json
from microsandbox import Sandbox
config = Sandbox.build_config(
"preview",
image="python",
memory=1024,
)
print(json.dumps(config, indent=2))
sb = await Sandbox.create(config)
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:
/.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.