> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ns.rocks/llms.txt
> Use this file to discover all available pages before exploring further.

# Volumes

> Create, mount, and manage persistent shared filesystems.

Volumes provide persistent shared filesystems that can be mounted into
machines 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

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
from nullspace import Machine, Volume

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

with Machine.create(volumes=[volume.mount("/workspace/shared")]) as machine:
    print(machine.files.read("/workspace/shared/hello.txt").strip())
```

Mount paths must be absolute machine paths outside `/workspace/.nullspace`.
Each machine can have up to 8 active volume attachments, and `subpath=` can
mount a volume subdirectory. `read_only=True` prevents writes through the
machine mount while still allowing direct `volume.files` writes by callers with
API access.

You can also attach volumes at create time with dictionaries:

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
machine = Machine.create(
    template="base",
    volumes=[
        {
            "ref": "team-data",
            "mount_path": "/workspace/shared",
            "subpath": "/datasets",
            "read_only": True,
        },
    ],
)
```

## Runtime attachments

Attach or detach volumes after a machine is already running:

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
from nullspace import Machine, Volume

machine = Machine.connect("mch_...")
volume = Volume.from_name("team-data")

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

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

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

remounted = machine.remount_volume(attachment.id)
released = machine.detach_volume(remounted.id)
print(released.state)
```

`machine.attach_volume(...)` accepts a volume ID, volume name, `VolumeMount`,
or dictionary. If `read_only` is omitted, the API defaults it to `False`.

List every machine attachment for a volume:

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
for attachment in volume.attachments():
    print(attachment.machine_id, attachment.mount_path, attachment.state)
```

Destroying a machine releases its remaining volume attachments.
`machine.pause()` and `machine.hibernate()` release live mount leases while
stopped, but preserve attachment intent and remount the same volumes on resume.
`AsyncMachine` exposes the same runtime volume methods with `await`.

## Direct volume files

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
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 machine `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

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
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

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
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.

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
nullspace volume list
nullspace volume list --fields id,name,mount_count,used_bytes
nullspace volume get team-data
nullspace machine 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](../../concepts/hibernate), [Resume](../../concepts/resume),
[Fork](../../concepts/fork). API reference:
[createVolume](../../api-reference), [listVolumes](../../api-reference),
[listVolumeFiles](../../api-reference), and [writeVolumeFile](../../api-reference).
