Back to Microsandbox

Volumes

docs/sandboxes/volumes.mdx

0.4.46.3 KB
Original Source

Volumes give a sandbox direct filesystem access to host-side directories. They're useful for persisting data across restarts, sharing data between sandboxes, or mounting host directories 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 three types of mounts: bind mounts, named volumes, and tmpfs.

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()) .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())
    .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),
        "/app/data": Volume.bind("./data"),
    },
)
bash
msb create python --name dev \
  -v ./src:/app/src:ro \
  -v ./data:/app/data
</CodeGroup>

Named volumes

Named volumes are managed by microsandbox and stored at ~/.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)
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),
    },
)
bash
msb create python --name worker \
  -v pip-cache:/root/.cache/pip
</CodeGroup>

Share between sandboxes

Named volumes are the simplest way to share data between sandboxes. Mount the same volume into multiple sandboxes: one writes, another reads.

<CodeGroup> ```rust Rust // Writer let writer = Sandbox::builder("writer") .image("alpine") .volume("/data", |v| v.named("shared-data")) .create() .await?; writer.shell("echo 'hello' > /data/message.txt").await?;

// Reader (read-only) let reader = Sandbox::builder("reader") .image("alpine") .volume("/data", |v| v.named("shared-data").readonly()) .create() .await?; let output = reader.exec("cat", ["/data/message.txt"]).await?; // => "hello"


```typescript TypeScript
// Writer
await using writer = await Sandbox.builder("writer")
    .image("alpine")
    .volume("/data", (m) => m.named("shared-data"))
    .create();
await writer.shell("echo 'hello' > /data/message.txt");

// Reader (read-only)
await using reader = await Sandbox.builder("reader")
    .image("alpine")
    .volume("/data", (m) => m.named("shared-data").readonly())
    .create();
const output = await reader.exec("cat", ["/data/message.txt"]);
// => "hello"
python
# Writer
writer = await Sandbox.create(
    "writer",
    image="alpine",
    volumes={"/data": Volume.named("shared-data")},
)
await writer.shell("echo 'hello' > /data/message.txt")

# Reader (read-only)
reader = await Sandbox.create(
    "reader",
    image="alpine",
    volumes={"/data": Volume.named("shared-data", readonly=True)},
)
output = await reader.exec("cat", ["/data/message.txt"])
# => "hello"
</CodeGroup> <Tip> When sharing volumes between sandboxes, both sandboxes access the same underlying host directory. There's no built-in locking, so if two sandboxes write to the same file concurrently, you'll get the usual race conditions. Use the read-only flag on the reader side to make intent explicit. </Tip>

Host-side access

Named volumes are accessible from the host even without a running sandbox, which is handy for pre-populating data before any sandbox mounts it.

<CodeGroup> ```rust Rust let vol = Volume::get("pip-cache").await?; vol.fs().write("/requirements.txt", "requests==2.28.0").await?; ```
typescript
const vol = await Volume.get("pip-cache");
await vol.fs().write("requirements.txt", "requests==2.28.0");
python
vol = await Volume.get("pip-cache")
await vol.fs.write("/requirements.txt", b"requests==2.28.0")
</CodeGroup>

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")
bash
msb volume ls
msb volume rm old-cache
</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)) .create() .await?; ```
typescript
import { Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("worker")
    .image("alpine")
    .volume("/tmp/scratch", (m) => m.tmpfs().size(100))
    .create();
python
sb = await Sandbox.create(
    "worker",
    image="alpine",
    volumes={
        "/tmp/scratch": Volume.tmpfs(size_mib=100),
    },
)
bash
msb create alpine --name worker \
  --tmpfs /tmp/scratch:100
</CodeGroup>