Back to Microsandbox

Volumes

docs/sandboxes/volumes.mdx

0.5.46.8 KB
Original Source

Volumes give a sandbox direct filesystem access to host-side storage or in-memory filesystems. They're useful for persisting data across restarts, sharing data between sandboxes, or mounting host data into the guest. They're also significantly faster than the filesystem API (which transfers files individually), so for anything beyond ad-hoc reads and writes, volumes are the way to go.

microsandbox supports four types of mounts: bind mounts, named volumes, tmpfs, and disk-image volumes.

Mounts are nosuid and nodev inside the guest by default. Add readonly / ro when the guest should not write, and add noexec when files on the mount should not be executed directly. User commands also run without mount-admin privilege, so ro and noexec cannot be undone with a guest remount. noexec does not stop interpreters from reading scripts from the mount, for example sh /app/src/script.sh.

Bind mounts

Mount a directory from the host directly into the sandbox. Changes inside the sandbox are reflected on the host, and vice versa.

<CodeGroup> ```rust Rust let sb = Sandbox::builder("dev") .image("python") .volume("/app/src", |v| v.bind("./src").readonly().noexec()) .volume("/app/data", |v| v.bind("./data")) .create() .await?; ```
typescript
import { Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("dev")
    .image("python")
    .volume("/app/src",  (m) => m.bind("./src").readonly().noexec())
    .volume("/app/data", (m) => m.bind("./data"))
    .create();
python
from microsandbox import Sandbox, Volume

sb = await Sandbox.create(
    "dev",
    image="python",
    volumes={
        "/app/src": Volume.bind("./src", readonly=True, noexec=True),
        "/app/data": Volume.bind("./data"),
    },
)
go
sb, err := m.CreateSandbox(ctx, "dev",
    m.WithImage("python"),
    m.WithMounts(map[string]m.MountConfig{
        "/app/src":  m.Mount.Bind("./src", m.MountOptions{Readonly: true, Noexec: true}),
        "/app/data": m.Mount.Bind("./data", m.MountOptions{}),
    }),
)
bash
msb create python --name dev \
  -v ./src:/app/src:ro,noexec \
  -v ./data:/app/data
</CodeGroup>

Named volumes

Named volumes are managed by microsandbox and stored by default under ~/.microsandbox/volumes/<name>/. They persist independently of any sandbox, so you can create a volume, populate it, and mount it into different sandboxes over time.

Create a volume

<CodeGroup> ```rust Rust let cache = Volume::builder("pip-cache") .quota(1024) .create() .await?; ```
typescript
import { Volume } from "microsandbox";

const cache = await Volume.builder("pip-cache").quota(1024).create();
python
cache = await Volume.create("pip-cache", quota_mib=1024)
go
cache, err := m.CreateVolume(ctx, "pip-cache",
    m.WithVolumeQuota(1024),
)
bash
msb volume create pip-cache --size 1024
</CodeGroup>

Mount in a sandbox

<CodeGroup> ```rust Rust let sb = Sandbox::builder("worker") .image("python") .volume("/root/.cache/pip", |v| v.named(cache.name())) .create() .await?; ```
typescript
await using sb = await Sandbox.builder("worker")
    .image("python")
    .volume("/root/.cache/pip", (m) => m.named(cache.name))
    .create();
python
sb = await Sandbox.create(
    "worker",
    image="python",
    volumes={
        "/root/.cache/pip": Volume.named(cache.name),
    },
)
go
sb, err := m.CreateSandbox(ctx, "worker",
    m.WithImage("python"),
    m.WithMounts(map[string]m.MountConfig{
        "/root/.cache/pip": m.Mount.Named(cache.Name(), m.MountOptions{}),
    }),
)
bash
msb create python --name worker \
  -v pip-cache:/root/.cache/pip
</CodeGroup>

Sharing and host-side access

Mount the same named volume into multiple sandboxes when they need to share files. Use readonly on consumers that should not write.

Named volumes are also accessible from the host without a running sandbox, so you can pre-populate a cache or inspect output after the sandbox stops. See the SDK references for filesystem helpers on volume handles.

Manage volumes

<CodeGroup> ```rust Rust let volumes = Volume::list().await?; Volume::remove("old-cache").await?; ```
typescript
const volumes = await Volume.list();
await Volume.remove("old-cache");
python
volumes = await Volume.list()
await Volume.remove("old-cache")
go
volumes, err := m.ListVolumes(ctx)
err = m.RemoveVolume(ctx, "old-cache")
bash
msb volume ls
msb volume rm old-cache
</CodeGroup>

Disk-image volumes

Disk-image volumes attach a host disk image as a virtio-blk device and mount its inner filesystem at a guest path. Use them when you want persistent state isolated from ordinary host filesystem metadata, or when distributing a prebuilt filesystem image. The disk format defaults from the file extension (.qcow2, .raw, .vmdk; otherwise raw), and the inner filesystem type is auto-detected unless you set fstype.

<CodeGroup> ```rust Rust let sb = Sandbox::builder("seeded") .image("alpine") .volume("/seed", |v| v.disk("./seed.raw").readonly().noexec()) .create() .await?; ```
typescript
await using sb = await Sandbox.builder("seeded")
    .image("alpine")
    .volume("/seed", (m) => m.disk("./seed.raw").readonly().noexec())
    .create();
python
sb = await Sandbox.create(
    "seeded",
    image="alpine",
    volumes={"/seed": Volume.disk("./seed.raw", readonly=True, noexec=True)},
)
go
sb, err := m.CreateSandbox(ctx, "seeded",
    m.WithImage("alpine"),
    m.WithMounts(map[string]m.MountConfig{
        "/seed": m.Mount.Disk("./seed.raw", m.DiskOptions{Readonly: true, Noexec: true}),
    }),
)
</CodeGroup>

Tmpfs

An in-memory filesystem that disappears when the sandbox stops. Useful for scratch space, build artifacts, or anything that doesn't need to outlive the sandbox.

<CodeGroup> ```rust Rust let sb = Sandbox::builder("worker") .image("alpine") .volume("/tmp/scratch", |v| v.tmpfs().size(100).noexec()) .create() .await?; ```
typescript
import { Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("worker")
    .image("alpine")
    .volume("/tmp/scratch", (m) => m.tmpfs().size(100).noexec())
    .create();
python
sb = await Sandbox.create(
    "worker",
    image="alpine",
    volumes={
        "/tmp/scratch": Volume.tmpfs(size_mib=100, noexec=True),
    },
)
go
sb, err := m.CreateSandbox(ctx, "worker",
    m.WithImage("alpine"),
    m.WithMounts(map[string]m.MountConfig{
        "/tmp/scratch": m.Mount.Tmpfs(m.TmpfsOptions{SizeMiB: 100, Noexec: true}),
    }),
)
bash
msb create alpine --name worker \
  --tmpfs /tmp/scratch:100:noexec
</CodeGroup>