Documentation/docs/volume

#Volume

Volume provides persistent storage for Sandbox0. It is a storage unit independent of the Sandbox lifecycle, allowing data sharing and reuse across multiple Sandboxes.

Why Volumes?#

The default Sandbox filesystem is ephemeral—when a Sandbox is deleted, all its data is lost. Volumes solve this problem:

  • Data Persistence: Store data that needs long-term retention, such as databases, model files, and user uploads
  • Cross-Sandbox Sharing: Mount the same Volume to multiple Sandboxes for data sharing
  • Fast Snapshots: Create point-in-time snapshots in seconds for backup and versioning
  • Fast Forking: Create independent child Volumes with Copy-on-Write isolation
  • Quick Recovery: Restore from snapshots quickly, ideal for rollbacks and environment cloning

Volume and Sandbox Relationship#

Access Modes#

Volumes support three access modes:

ModeFull NameDescriptionTypical Use Cases
RWORead-Write OnceOne writable owner at a timeAgent workspaces, databases, exclusive write-heavy state
ROXRead-Only CrossMulti-sandbox read-only distributionShared model files, static assets, reference datasets
RWXRead-Write CrossShared read-write volume mode for direct API workflowsControl-plane style file workflows and shared storage patterns that do not rely on sandbox mounts

The default access mode is RWO. Sandbox mounts are declared by the template and bound at claim time. RWO mounts use node-local write-ahead logging for low-latency small-file workloads, ROX is for read-only sharing, and RWX is not accepted for sandbox mounts in the current node-local mount path.

Correctness Model#

For mounted RWO volumes, Sandbox0 keeps one active writable owner at a time.

  • when a sandbox mounts an RWO volume, the node-local mount owner becomes authoritative for reads and writes
  • direct volume file API requests are routed to that mounted owner instead of opening a second writable mount
  • when the volume is not mounted into a sandbox, storage-proxy serves direct file API requests itself

This keeps the mounted filesystem view and the direct volume API view consistent in both directions.

Application-Layer Encryption#

Sandbox0 encrypts persisted S0FS volume data at the application layer before it is written to object storage.

Manifest objects are encrypted as full blobs. Segment objects are encrypted in independently authenticated chunks, so cold file reads can still fetch only the ciphertext chunks needed for the requested byte range. Node-local cache files, including the S0FS WAL, head state, and snapshot state, are also encrypted before they are written to disk.

This is service-side application-layer encryption. storage-proxy and ctld hold the volume encryption key so they can serve normal POSIX filesystem reads and writes. It is not end-to-end encryption where Sandbox0 services cannot decrypt volume data.

For self-hosted deployments, see Self-hosted Configuration for the storage-proxy encryption setting.

Direct File Operations#

You can operate on files in a Volume directly by volume ID, without mounting the Volume into a Sandbox first.

This is useful for SDK workflows, developer tooling, and small control-plane style file tasks where starting or reusing a Sandbox would only add latency.

Direct volume file APIs still go through the normal gateway chain and team-scoped auth. When a volume is already mounted into a sandbox, the API request is served by that mounted owner. When it is not mounted, storage-proxy lazily attaches the volume on demand and reclaims idle direct mounts later.

The direct file API surface mirrors the Sandbox file API:

OperationEndpoint
Read / Write / DeleteGET, POST, DELETE /api/v1/sandboxvolumes/{'{id}'}/files?path=...
StatGET /api/v1/sandboxvolumes/{'{id}'}/files/stat?path=...
List directoryGET /api/v1/sandboxvolumes/{'{id}'}/files/list?path=...
Move / RenamePOST /api/v1/sandboxvolumes/{'{id}'}/files/move
WatchGET /api/v1/sandboxvolumes/{'{id}'}/files/watch

Paths are always resolved relative to the root of the Volume namespace.

For SDK, CLI, and direct HTTP file workflows, see the dedicated Volume HTTP page.


Create Volume#

Create a new persistent volume with an access mode.

POST

/api/v1/sandboxvolumes

go
volume, err := client.CreateVolume(ctx, apispec.CreateSandboxVolumeRequest{ AccessMode: apispec.NewOptVolumeAccessMode(apispec.VolumeAccessModeRWO), }) if err != nil { log.Fatal(err) } fmt.Printf("Volume ID: %s\n", volume.ID)

Get Volume Details#

Retrieve a specific volume by ID.

GET

/api/v1/sandboxvolumes/{id}

go
vol, err := client.GetVolume(ctx, volume.ID) if err != nil { log.Fatal(err) } fmt.Printf("Volume: %s (mode: %s)\n", vol.ID, vol.AccessMode.Value)

List Volumes#

List all volumes in the current team.

GET

/api/v1/sandboxvolumes

go
volumes, err := client.ListVolume(ctx) if err != nil { log.Fatal(err) } for _, v := range volumes { fmt.Printf("- %s (%s)\n", v.ID, v.AccessMode.Value) }

Delete Volume#

Delete a volume when it is no longer needed.

DELETE

/api/v1/sandboxvolumes/{id}

go
_, err = client.DeleteVolume(ctx, volume.ID) if err != nil { log.Fatal(err) } fmt.Println("Volume deleted")

Persist Runtime Environment with Nix#

You can use Nix + Volume to persist and reuse your runtime environment artifacts across Sandboxes.

  • Can persist: dependency closures, build caches, package stores, lock files, and workspace-level toolchains
  • Cannot persist: live process runtime state (in-memory variables, process stacks, open sockets, PID state)

This means:

  • A new Sandbox can quickly reproduce the same environment from mounted Volume data
  • But it still starts as a new process runtime, not a paused/resumed OS process image

Next Steps#

Mounts

Mount volumes into sandbox templates and claims with correct access modes.

HTTP

Use direct volume file APIs outside a running sandbox mount.