> ## Documentation Index
> Fetch the complete documentation index at: https://microsanbox-staging-toks-cloud-snapshot-contracts.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Volumes

> Persist and share data across sandboxes

<Note>
  **Cloud:** Volumes use fast block storage managed by the platform. Every organization automatically gets an undeletable **host volume**: think of it as the disk of the one computer all its sandboxes run on, and it is what bind and disk-image mounts resolve against.
</Note>

Volumes give a sandbox persistent or shared storage by mounting host data, managed storage, disk images, or in-memory filesystems into the guest. Mounts provide direct filesystem access and are significantly faster than the [filesystem API](/sandboxes/filesystem), which transfers files individually, so they are preferable for anything beyond ad-hoc reads and writes.

microsandbox supports four mount types on both backends: bind mounts, named volumes, disk-image volumes, and tmpfs.

## Cloud default volume

<Tooltip tip="Available only on microsandbox cloud; the local backend has no default volume."><span className="msb-badge-cloud">Cloud-only <Icon icon="circle-info" size={11} /></span></Tooltip>

Every Cloud organization has an always-present default volume. You can read and write it directly without starting a sandbox:

<CodeGroup>
  ```rust Rust theme={null}
  let volume = Volume::get_default().await?;
  volume.fs().write("state.json", br#"{"ready":true}"#).await?;
  ```

  ```typescript TypeScript theme={null}
  const volume = await Volume.getDefault();
  await volume.fs().write("state.json", JSON.stringify({ ready: true }));
  ```

  ```python Python theme={null}
  volume = await Volume.get_default()
  await volume.fs().write("state.json", b'{"ready": true}')
  ```

  ```go Go theme={null}
  volume, err := m.GetDefaultVolume(ctx)
  err = volume.FS().WriteString("state.json", `{"ready":true}`)
  ```
</CodeGroup>

The default volume is isolated to the authenticated Cloud organization and cannot be removed. Default-volume lookup is deliberately unsupported on the local backend: forgetting an API key cannot expose or modify files from your machine. Named local volumes continue to live under `~/.microsandbox/volumes/` and retain their existing direct filesystem API.

## Bind mounts

<Tooltip tip="On microsandbox cloud, the host path resolves against your organization's host volume, not the computer running the SDK or CLI."><span className="msb-badge-limited">Limited on cloud <Icon icon="circle-info" size={11} /></span></Tooltip>

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 theme={null}
  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 TypeScript theme={null}
  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 Python theme={null}
  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 Go theme={null}
  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 CLI theme={null}
  msb create python --name dev \
    --mount-dir ./src:/app/src:ro,noexec \
    --mount-dir ./data:/app/data
  ```
</CodeGroup>

Nested destinations are supported. microsandbox canonicalizes guest paths and always applies enclosing mounts before their children, so SDKs that accept unordered maps—such as Go—produce the same result regardless of map iteration order.

Use `--mount-file` when you want to bind one host file instead of an entire directory.

File mounts expose the selected host inode through an isolated one-entry filesystem. They do not hard-link or copy the source, host changes remain visible, and other files in the source directory are not part of the guest-visible share. Writable file mounts receive the same default quota as directory binds; use `quota=<size>` to set a smaller or larger guest-growth budget.

```bash theme={null}
msb create alpine --name config-reader \
  --mount-file ./config.toml:/etc/app/config.toml:ro,nodev
```

## Named volumes

<Tooltip tip="On microsandbox cloud, create named volumes before mounting them; create-on-mount, disk-kind volumes, and size are not available, and quota must be a whole number of GiB."><span className="msb-badge-limited">Limited on cloud <Icon icon="circle-info" size={11} /></span></Tooltip>

microsandbox manages named volumes and stores them 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.

Named volumes can be directory-backed or disk-backed. Directory volumes mount through virtiofs. Disk volumes are raw ext4 disk images managed by microsandbox and mount through virtio-blk.

CLI named mounts are idempotent. `-v name:/path` and `--mount-named name:/path` create a directory-backed named volume if it does not already exist, then mount it. If the volume already exists, microsandbox reuses it when the requested storage settings are compatible and errors when they are not. Use `--mount-named name:/path:kind=disk,size=20G` to create or reuse a disk-backed named volume from the mount flag itself.

SDK `named(...)` mount helpers are existing-only. They fail if the named volume does not already exist. Use each SDK's explicit named-volume mode API when you want sandbox creation to create or ensure the volume.

### Create a volume

<CodeGroup>
  ```rust Rust theme={null}
  let cache = Volume::builder("pip-cache")
      .create()
      .await?;
  ```

  ```typescript TypeScript theme={null}
  import { Volume } from "microsandbox";

  const cache = await Volume.builder("pip-cache").create();
  ```

  ```python Python theme={null}
  cache = await Volume.create("pip-cache")
  ```

  ```go Go theme={null}
  cache, err := m.CreateVolume(ctx, "pip-cache")
  ```

  ```bash CLI theme={null}
  msb volume create pip-cache
  ```
</CodeGroup>

Create a disk-backed named volume when a workload needs a real guest block filesystem, such as Docker's data root:

<CodeGroup>
  ```rust Rust theme={null}
  let docker_data = Volume::builder("docker-data")
      .disk()
      .size(20.gib())
      .create()
      .await?;
  ```

  ```typescript TypeScript theme={null}
  const dockerData = await Volume.builder("docker-data")
    .disk()
    .size(20 * 1024)
    .create();
  ```

  ```python Python theme={null}
  from microsandbox import VolumeKind

  docker_data = await Volume.create(
      "docker-data",
      kind=VolumeKind.DISK,
      size_mib=20 * 1024,
  )
  ```

  ```go Go theme={null}
  dockerData, err := m.CreateVolume(ctx, "docker-data",
      m.WithVolumeKind(m.VolumeKindDisk),
      m.WithVolumeSize(20 * 1024),
  )
  ```

  ```bash CLI theme={null}
  msb volume create docker-data --kind disk --size 20G
  ```
</CodeGroup>

### Mount in a sandbox

<CodeGroup>
  ```rust Rust theme={null}
  let sb = Sandbox::builder("worker")
      .image("python")
      .volume("/root/.cache/pip", |v| v.named(cache.name()))
      .create()
      .await?;
  ```

  ```typescript TypeScript theme={null}
  await using sb = await Sandbox.builder("worker")
      .image("python")
      .volume("/root/.cache/pip", (m) => m.named(cache.name))
      .create();
  ```

  ```python Python theme={null}
  sb = await Sandbox.create(
      "worker",
      image="python",
      volumes={
          "/root/.cache/pip": Volume.named(cache.name),
      },
  )
  ```

  ```go Go theme={null}
  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 CLI theme={null}
  msb create python --name worker \
    --mount-named pip-cache:/root/.cache/pip
  ```
</CodeGroup>

The CLI command above creates `pip-cache` as a directory-backed named volume if it is missing. To create or reuse a disk-backed named volume while mounting it:

```bash theme={null}
msb create docker:dind --name docker-demo \
  --mount-named docker-data:/var/lib/docker:kind=disk,size=20G
```

### Sharing and host-side access

Mount the same directory-backed named volume into multiple sandboxes when they need to share files. Use `readonly` on consumers that should not write. Disk-backed named volumes are attached as block devices; use one writable sandbox at a time, or read-only mounts where sharing is intended.

Directory-backed named volumes are also accessible without a running sandbox, so you can pre-populate a cache or inspect output after the sandbox stops. Local volumes expose their host path directly; cloud default and managed directory volumes use the same volume filesystem API shown above. Disk-backed local volumes store guest data inside `disk.raw`; host filesystem helpers operate on the managed volume directory, not the filesystem inside the disk image.

### Manage volumes

<CodeGroup>
  ```rust Rust theme={null}
  let volumes = Volume::list().await?;
  Volume::remove("old-cache").await?;
  ```

  ```typescript TypeScript theme={null}
  const volumes = await Volume.list();
  await Volume.remove("old-cache");
  ```

  ```python Python theme={null}
  volumes = await Volume.list()
  await Volume.remove("old-cache")
  ```

  ```go Go theme={null}
  volumes, err := m.ListVolumes(ctx)
  err = m.RemoveVolume(ctx, "old-cache")
  ```

  ```bash CLI theme={null}
  msb volume ls
  msb volume rm old-cache
  ```
</CodeGroup>

## Disk-image volumes

<Tooltip tip="On microsandbox cloud, the host path resolves against your organization's host volume, not the computer running the SDK or CLI."><span className="msb-badge-limited">Limited on cloud <Icon icon="circle-info" size={11} /></span></Tooltip>

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 theme={null}
  let sb = Sandbox::builder("seeded")
      .image("alpine")
      .volume("/seed", |v| v.disk("./seed.raw").readonly().noexec())
      .create()
      .await?;
  ```

  ```typescript TypeScript theme={null}
  await using sb = await Sandbox.builder("seeded")
      .image("alpine")
      .volume("/seed", (m) => m.disk("./seed.raw").readonly().noexec())
      .create();
  ```

  ```python Python theme={null}
  sb = await Sandbox.create(
      "seeded",
      image="alpine",
      volumes={"/seed": Volume.disk("./seed.raw", readonly=True, noexec=True)},
  )
  ```

  ```go Go theme={null}
  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}),
      }),
  )
  ```

  ```bash CLI theme={null}
  msb create alpine --name seeded \
    --mount-disk ./seed.raw:/seed:ro,noexec,fstype=ext4
  ```
</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 theme={null}
  let sb = Sandbox::builder("worker")
      .image("alpine")
      .volume("/tmp/scratch", |v| v.tmpfs().size(100).noexec())
      .create()
      .await?;
  ```

  ```typescript TypeScript theme={null}
  import { Sandbox } from "microsandbox";

  await using sb = await Sandbox.builder("worker")
      .image("alpine")
      .volume("/tmp/scratch", (m) => m.tmpfs().size(100).noexec())
      .create();
  ```

  ```python Python theme={null}
  sb = await Sandbox.create(
      "worker",
      image="alpine",
      volumes={
          "/tmp/scratch": Volume.tmpfs(size_mib=100, noexec=True),
      },
  )
  ```

  ```go Go theme={null}
  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 CLI theme={null}
  msb create alpine --name worker \
    --tmpfs /tmp/scratch:100:noexec
  ```
</CodeGroup>

## Mount options

Mounts use normal Linux behavior by default: writable, executable, suid-enabled, and device-enabled. Adjust with:

* `readonly` / `ro` when the guest should not write to the mount.
* `noexec` when files on the mount should not be executed directly.
* `nosuid` when setuid/setgid bits should be ignored.
* `nodev` when device files should be ignored.

`noexec` does not stop interpreters from reading scripts from the mount, for example `sh /app/src/script.sh`.

For stronger in-guest hardening, create the sandbox with the restricted security profile. Restricted sandboxes set `no_new_privs`, drop mount-admin capability from user commands, and force `nosuid,nodev` on user mounts. This profile is incompatible with workloads such as `sudo` and Docker-in-Docker.

### Fallback guest ownership

Bind mounts and directory-backed named volumes normally present host files without a per-file metadata override using the sandbox user's fallback identity. Set a mount owner when those files should consistently appear as another guest `(uid, gid)` pair. This changes only the guest view: it does not `chown` the host files or create per-file metadata overrides, and an existing per-file override still takes precedence.

Both IDs are required together and may range from `0` through `4294967295`. Mount owners require stat virtualization, so they cannot be combined with `stat-virt=off`, tmpfs, host disk images, or disk-backed named volumes. The local backend supports this policy; the Cloud backend currently rejects it until ownership capability negotiation is available.

### CLI syntax and flags

CLI mount flags use `SOURCE:DEST[:OPTIONS]`. The `:` before `OPTIONS` starts the option block, and commas separate options inside that block.

| Flag            | Source          | Options                                                                                                                                                                                                                                                              |
| --------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--mount-dir`   | Host directory  | `ro`, `rw`, `noexec`, `nosuid`, `nodev`, `stat-virt=strict\|relaxed\|off`, `host-perms=private\|mirror`, `quota=<size>`, paired `uid=<N>,gid=<N>`                                                                                                                    |
| `--mount-file`  | Host file       | `ro`, `rw`, `noexec`, `nosuid`, `nodev`, `stat-virt=strict\|relaxed\|off`, `host-perms=private\|mirror`, `quota=<size>`, paired `uid=<N>,gid=<N>`                                                                                                                    |
| `--mount-named` | Named volume    | `ro`, `rw`, `noexec`, `nosuid`, `nodev`, `kind=dir\|disk`, `size=<size>` for `kind=disk`, `quota=<size>` for directory volumes; directory-backed named volumes also support `stat-virt=strict\|relaxed\|off`, `host-perms=private\|mirror`, paired `uid=<N>,gid=<N>` |
| `--mount-disk`  | Host disk image | `ro`, `rw`, `noexec`, `nosuid`, `nodev`, `format=raw\|qcow2\|vmdk`, `fstype=<type>`                                                                                                                                                                                  |
| `--tmpfs`       | Guest path      | `ro`, `rw`, `noexec`, `nosuid`, `nodev`; size may be passed as `PATH:SIZE[:OPTIONS]`                                                                                                                                                                                 |

## Combining mounts

Mount flags may be repeated, so a sandbox can combine host directories, individual files, named volumes, disk images, and tmpfs scratch space.

<CodeGroup>
  ```rust Rust theme={null}
  let sb = Sandbox::builder("full")
      .image("python")
      .volume("/app/src", |v| v.bind("./src").readonly().noexec())
      .volume("/app/pyproject.toml", |v| v.bind("./pyproject.toml").readonly())
      .volume("/root/.cache/pip", |v| {
          v.named("pip-cache")
              .stat_virtualization(StatVirtualization::Relaxed)
      })
      .volume("/dataset", |v| {
          v.disk("./dataset.qcow2")
              .format(DiskImageFormat::Qcow2)
              .fstype("ext4")
              .readonly()
      })
      .volume("/tmp", |v| v.tmpfs().size(1024).noexec().nosuid().nodev())
      .create()
      .await?;
  ```

  ```bash CLI theme={null}
  msb create python --name full \
    --mount-dir ./src:/app/src:ro,noexec \
    --mount-file ./pyproject.toml:/app/pyproject.toml:ro \
    --mount-named pip-cache:/root/.cache/pip:stat-virt=relaxed \
    --mount-disk ./dataset.qcow2:/dataset:ro,format=qcow2,fstype=ext4 \
    --tmpfs /tmp:1G:noexec,nosuid,nodev
  ```
</CodeGroup>
