Back to Microsandbox

Overview

docs/sandboxes/overview.mdx

0.5.57.2 KB
Original Source

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.

Create a sandbox

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.

<CodeGroup> ```rust Rust let sb = Sandbox::builder("worker") .image("python") .create() .await?; ```
typescript
await using sb = await Sandbox.builder("worker")
    .image("python")
    .create();
python
sb = await Sandbox.create("worker", image="python")
go
sb, err := m.CreateSandbox(ctx, "worker", m.WithImage("python"))
bash
msb create python --name worker
</CodeGroup>

Sandbox names must be non-empty and no longer than 128 UTF-8 bytes.

Common configuration

<CodeGroup> ```rust Rust let sb = Sandbox::builder("worker") .image("python") .memory(1024) .cpus(2) .env("DEBUG", "true") .label("user.id", "alice") .volume("/app/src", |v| v.bind("./src").readonly()) .create() .await?; ```
typescript
await using sb = await Sandbox.builder("worker")
    .image("python")
    .memory(1024)
    .cpus(2)
    .env("DEBUG", "true")
    .label("user.id", "alice")
    .volume("/app/src", (m) => m.bind("./src").readonly())
    .create();
python
sb = await Sandbox.create(
    "worker",
    image="python",
    memory=1024,
    cpus=2,
    env={"DEBUG": "true"},
    labels={"user.id": "alice"},
    volumes={"/app/src": Volume.bind("./src", readonly=True)},
)
go
sb, err := m.CreateSandbox(ctx, "worker",
    m.WithImage("python"),
    m.WithMemory(1024),
    m.WithCPUs(2),
    m.WithEnv(map[string]string{"DEBUG": "true"}),
    m.WithLabels(map[string]string{"user.id": "alice"}),
    m.WithMounts(map[string]m.MountConfig{
        "/app/src": m.Mount.Bind("./src", m.MountOptions{Readonly: true}),
    }),
)
bash
msb create python --name worker \
  -c 2 \
  -m 1G \
  -e DEBUG=true \
  --label user.id=alice \
  -v ./src:/app/src:ro
</CodeGroup>
OptionDefaultDescription
imagerequiredOCI image, local rootfs path, or disk image
cpus1Virtual CPU limit
memory512Guest memory limit in MiB
workdirimage defaultDefault working directory for commands
shell/bin/shShell used by shell() calls
envemptyEnvironment variables
labelsemptyKey/value metadata for metric attribution
volumesemptyBind, named, tmpfs, or disk-image mounts
networkpublic-onlyNetwork policy and published ports
scriptsemptyNamed scripts mounted at /.msb/scripts/

Labels are arbitrary key=value metadata attached to a sandbox, set at creation and fixed for the sandbox's life. They are meant for attribution — tagging a sandbox with the user, tenant, or environment it belongs to so the metrics pipeline can build per-user views. Keys cannot start with the reserved prefixes sandbox., microsandbox., or service..

Set one at a time with .label(key, value) or many at once with .labels({...}) (WithLabel / WithLabels in Go, the labels={...} argument in Python). On the CLI, repeat --label once per entry. Values may be empty: a valueless label such as --label gpu is a plain marker (it stores gpu=""), matching Docker's label semantics.

The OCI image's own labels (LABEL instructions, org.opencontainers.image.*, etc.) are imported automatically as sandbox labels at create time; a label you set yourself overrides the image's value on a key collision. Image labels using a reserved prefix are skipped. Note this can add series cardinality if an image carries high-cardinality labels (a commit SHA, a build timestamp); set --no-labels on msb-metrics if that is a concern.

Selecting sandboxes by label

Labels double as selectors. list_with returns sandboxes carrying all of the given labels (AND-matched) — useful when your application owns a subset of sandboxes on a shared host. The CLI extends this to bulk operations: msb stop, msb start, and msb rm all accept --label to act on every match.

<CodeGroup> ```rust Rust use microsandbox::sandbox::{Sandbox, SandboxFilter};

let mine = Sandbox::list_with(SandboxFilter::new().label("app", "engine")).await?;


```typescript TypeScript
const mine = await Sandbox.listWith({ labels: { app: "engine" } });
python
mine = await Sandbox.list_with(labels={"app": "engine"})
go
mine, err := microsandbox.ListSandboxesWith(ctx,
    microsandbox.NewSandboxFilter().WithLabels(map[string]string{"app": "engine"}))
bash
msb ls --label app=engine      # list matches
msb stop --label app=engine    # stop every match
msb rm --force --label app=engine
</CodeGroup> <Tip> `cpus` and `memory` are limits, not reservations. Guest memory is allocated as the VM touches pages. </Tip>

Image sources

Most sandboxes start from an OCI image:

bash
msb create python --name worker

You can also use a host directory as the root filesystem:

bash
msb create ./my-rootfs --name worker

Or boot from a disk image:

bash
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.

Naming conflicts

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?; ```
typescript
await using sb = await Sandbox.builder("worker")
    .image("python")
    .replace()
    .create();
python
sb = await Sandbox.create("worker", image="python", replace=True)
go
sb, err := m.CreateSandbox(ctx, "worker",
    m.WithImage("python"),
    m.WithReplace(),
)
bash
msb create --replace python --name worker
</CodeGroup>

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.

Where to go next