Skip to main content
Volumes provide persistent shared filesystems that can be mounted into sandboxes and managed directly through volume.files.
Volumes require a microVM-runtime deployment with the shared-volume backend and Supabase-backed control-plane state enabled.

Create and mount

from nullspace import Sandbox, Volume

volume = Volume.from_name("team-data", create_if_missing=True)
volume.files.write("/hello.txt", "persistent\n")

with Sandbox.create(volumes=[volume.mount("/workspace/shared")]) as sandbox:
    print(sandbox.files.read("/workspace/shared/hello.txt").strip())
Mount paths must be absolute sandbox paths outside /workspace/.nullspace. Each sandbox can have up to 8 active volume attachments, and subpath= can mount a volume subdirectory. read_only=True prevents writes through the sandbox mount while still allowing direct volume.files writes by callers with API access. You can also attach volumes at create time with dictionaries:
sandbox = Sandbox.create(
    template="base",
    volumes=[
        {
            "ref": "team-data",
            "mount_path": "/workspace/shared",
            "subpath": "/datasets",
            "read_only": True,
        },
    ],
)

Runtime attachments

Attach or detach volumes after a sandbox is already running:
from nullspace import Sandbox, Volume

sandbox = Sandbox.connect("sb_...")
volume = Volume.from_name("team-data")

attachment = sandbox.attach_volume(
    volume.id,
    "/workspace/shared",
    subpath="/datasets",
    read_only=True,
)

for item in sandbox.list_volumes():
    print(item.id, item.volume_id, item.mount_path, item.state)

health = sandbox.get_volume_health(attachment.id)
if health.state == "failed":
    print(health.last_error_code, health.last_error_message)

remounted = sandbox.remount_volume(attachment.id)
released = sandbox.detach_volume(remounted.id)
print(released.state)
sandbox.attach_volume(...) accepts a volume ID, volume name, VolumeMount, or dictionary. If read_only is omitted, the API defaults it to False. List every sandbox attachment for a volume:
for attachment in volume.attachments():
    print(attachment.sandbox_id, attachment.mount_path, attachment.state)
Destroying a sandbox releases its remaining volume attachments. sandbox.pause() and sandbox.hibernate() release live mount leases while stopped, but preserve attachment intent and remount the same volumes on resume. AsyncSandbox exposes the same runtime volume methods with await.

Direct volume files

volume.files.make_dir("/datasets")
volume.files.write_files([
    ("/datasets/a.txt", "ready\n"),
    ("/datasets/b.txt", "ready\n"),
])
volume.files.write("/datasets/report.txt", "ready\n")
print(volume.files.read("/datasets/report.txt"))
paths = volume.files.search_files("/datasets", "*.txt")
result = volume.files.replace_in_files(paths, "ready", "done", dry_run=True)
print(result.replacements, result.files_modified)
volume.files.remove("/datasets/report.txt")
Direct volume file paths are absolute volume-internal paths rooted at /. Relative paths are rejected, .. cannot escape the root, and direct volume operations do not use sandbox cwd or user semantics. write_files() raises BatchWriteError if any file fails after earlier writes may already have succeeded. Inspect successes and failures before retrying.

Volume lifecycle helpers

created = Volume.create("team-cache")
same = Volume.get_by_name("team-cache")
info = same.get_info()
for item in Volume.list():
    print(item.id, item.name, item.used_bytes, item.max_size_bytes)
created.delete()
Use Volume.from_id(id) when you already have a volume ID, and Volume.from_name(name, create_if_missing=True) when setup scripts should be idempotent. Volume.list(fields=[...]), Volume.get(id, fields=[...]), and Volume.get_by_name(name, fields=[...]) return raw dictionaries containing only the requested metadata fields.

Async API

from nullspace import AsyncVolume

volume = await AsyncVolume.from_name("team-data", create_if_missing=True)
await volume.files.write("/hello.txt", "persistent\n")
print(await volume.files.read("/hello.txt"))
AsyncVolume.files mirrors the synchronous direct file and transfer API. VolumeInfo.max_size_bytes is the current enforced cap. In the hosted private beta, auto-grow is currently disabled and the default cap is 2 GiB per volume unless an operator has applied a tenant-specific override. Refresh volume metadata after large writes when you need the latest capacity view.

CLI

The CLI commands below cover create-time mounts and direct file operations for teams that prefer shell workflows. Use the Python SDK or HTTP API for runtime attach, detach, health, and remount operations.
nullspace volume list
nullspace volume list --fields id,name,mount_count,used_bytes
nullspace volume get team-data
nullspace sandbox create --template base --volume ref=team-data,mount=/data,subpath=/datasets,ro=true
nullspace volume ls-files team-data /
nullspace volume cat team-data /hello.txt
printf "hello" | nullspace volume write team-data /hello.txt
nullspace volume write-files team-data '[["/a.txt","a\n"],["/b.txt","b\n"]]'
nullspace volume read team-data /hello.txt
nullspace volume mkdir team-data /datasets
nullspace volume stat team-data /datasets/report.txt
nullspace volume info team-data /datasets/report.txt
nullspace volume exists team-data /datasets/report.txt
nullspace volume mv team-data /datasets/report.txt /datasets/report-old.txt
nullspace volume chmod team-data /datasets/report-old.txt 0644
nullspace volume rm team-data /datasets/report-old.txt
nullspace volume find team-data /datasets "needle"
nullspace volume search team-data /datasets "*.json"
nullspace volume replace team-data "old" "new" /datasets/config.json --dry-run
nullspace volume watch team-data /datasets --recursive --timeout 30
nullspace volume upload-url team-data /models/model.bin
nullspace volume download-url team-data /models/model.bin
nullspace volume upload team-data ./model.bin /models/model.bin
nullspace volume download team-data /models/model.bin ./model.bin --force
nullspace volume delete vol_12345678

Snapshot behavior

Shared volume data is durable storage external to VM memory and mutable rootfs snapshots. Hibernate, resume, and fork perform storage remounting, not memory portability. They do not make live VM memory or mutable rootfs snapshot state portable across incompatible runtime hosts. Concepts: Hibernate, Resume, Fork. API reference: createVolume, listVolumes, listVolumeFiles, and writeVolumeFile.