docs/sandboxes/overview.mdx
A sandbox is a local microVM with its own Linux kernel, filesystem, and network stack. Your application or the msb CLI starts it as a child process, then talks to the guest agent to run commands, move files, and control lifecycle.
The security boundary is hardware virtualization, not Linux namespaces. That makes sandboxes a good fit for running agent-generated code, untrusted tools, dependency installs, and other workloads that should not inherit the host process's full privileges.
At minimum, a sandbox needs a name and an image. Everything else has defaults: 1 vCPU, 512 MiB memory, public-only networking, and /bin/sh as the default shell.
await using sb = await Sandbox.builder("worker")
.image("python")
.create();
sb = await Sandbox.create("worker", image="python")
sb, err := m.CreateSandbox(ctx, "worker", m.WithImage("python"))
msb create python --name worker
Sandbox names must be non-empty and no longer than 128 UTF-8 bytes.
await using sb = await Sandbox.builder("worker")
.image("python")
.memory(1024)
.cpus(2)
.env("DEBUG", "true")
.volume("/app/src", (m) => m.bind("./src").readonly())
.create();
sb = await Sandbox.create(
"worker",
image="python",
memory=1024,
cpus=2,
env={"DEBUG": "true"},
volumes={"/app/src": Volume.bind("./src", readonly=True)},
)
sb, err := m.CreateSandbox(ctx, "worker",
m.WithImage("python"),
m.WithMemory(1024),
m.WithCPUs(2),
m.WithEnv(map[string]string{"DEBUG": "true"}),
m.WithMounts(map[string]m.MountConfig{
"/app/src": m.Mount.Bind("./src", m.MountOptions{Readonly: true}),
}),
)
msb create python --name worker \
-c 2 \
-m 1G \
-e DEBUG=true \
-v ./src:/app/src:ro
| Option | Default | Description |
|---|---|---|
image | required | OCI image, local rootfs path, or disk image |
cpus | 1 | Virtual CPU limit |
memory | 512 | Guest memory limit in MiB |
workdir | image default | Default working directory for commands |
shell | /bin/sh | Shell used by shell() calls |
env | empty | Environment variables |
volumes | empty | Bind, named, tmpfs, or disk-image mounts |
network | public-only | Network policy and published ports |
scripts | empty | Named scripts mounted at /.msb/scripts/ |
Most sandboxes start from an OCI image:
msb create python --name worker
You can also use a host directory as the root filesystem:
msb create ./my-rootfs --name worker
Or boot from a disk image:
msb create ./alpine.qcow2 --name worker
OCI images use a copy-on-write overlay so sandboxes can share cached base layers. Disk images are attached as block devices, so each sandbox should use its own disk image copy unless the image format handles its own snapshotting.
See Images and Disk Images for the full image model.
Creating a sandbox fails if another sandbox already has the same name. Use replace when you want a fresh sandbox with that name:
<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)
sb, err := m.CreateSandbox(ctx, "worker",
m.WithImage("python"),
m.WithReplace(),
)
msb create --replace python --name worker
When replacing a running sandbox, microsandbox attempts graceful shutdown before force-killing it. Use replace_with_timeout or --replace-with-timeout when the workload needs a longer grace period.