#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 writable filesystem is checkpointed across pause/resume for the same Sandbox identity. Sandbox rootfs snapshots and forks can preserve or branch that rootfs state, but they still operate at the sandbox rootfs boundary. Volumes solve the cases where data must be mounted by multiple Sandboxes or accessed directly through storage APIs:
- 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
| Storage surface | Survives pause/resume | Survives sandbox delete or hard_ttl | Shareable | Snapshots and forks | Direct file API |
|---|---|---|---|---|---|
| Sandbox root filesystem | Yes, after checkpointed pause | Only through named rootfs snapshots or forks | No | Yes, through Snapshot And Restore | Through the Sandbox file API while the Sandbox exists |
| Sandbox Volume | Yes | Yes, until the Volume is deleted | Yes | Backend-dependent; s0fs only | Backend-dependent; s0fs always, s3 only through an active mount owner |
Volume and Sandbox Relationship#
Access Modes#
Volumes support three access modes:
| Mode | Full Name | Description | Typical Use Cases |
|---|---|---|---|
RWO | Read-Write Once | One writable owner at a time | Agent workspaces, databases, exclusive write-heavy state |
ROX | Read-Only Cross | Multi-sandbox read-only distribution | Shared model files, static assets, reference datasets |
RWX | Read-Write Cross | Shared read-write volume mode for direct API workflows | Control-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.
Volume Backends#
The default backend is s0fs, Sandbox0's durable POSIX-oriented volume store backed by S3-compatible object storage. Sandbox0 also supports an s3 backend for mounting an existing S3-compatible bucket prefix into a Sandbox.
Backend choice controls which operations are available. s0fs supports snapshots, restore, forks, and direct file APIs without a running Sandbox. s3 is a mount-oriented object prefix projection and does not support snapshots, restore, or forks.
For the full compatibility matrix and S3 backend examples, see Volume Backends.
Metered Storage Usage#
Volume detail and list responses expose the latest storage observation for s0fs volumes:
metered_storage_bytesis the metered logical payload size in bytesstorage_observed_atis the time that size was observed
Both fields are null for external s3 volumes and when a current metering projection is unavailable. These fields describe the latest current size; they are separate from historical sandbox.volume_byte_hours usage windows.
For an s0fs mount, filesystem tools such as df report the current logical file bytes against a stable 4 TiB virtual capacity. Object storage does not expose a meaningful per-Volume disk size, so this virtual value is not a storage allocation, hard quota, or node filesystem capacity. Use metered_storage_bytes for the authoritative current logical payload size; platform quotas and ctld node-local cache safeguards are separate controls.
Correctness Model#
For mounted RWO volumes, Sandbox0 keeps one active writable owner at a time.
- when a sandbox mounts an
RWOvolume, 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
- for
s0fsvolumes that are not mounted into a sandbox, the storage runtime in manager serves direct file API requests itself
This keeps the mounted filesystem view and the direct volume API view consistent in both directions. For s3 backend Volumes, direct file API requests require an active ctld mount owner; otherwise use S3 APIs directly or mount the Volume into a Sandbox first.
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. The storage runtime in manager and the active ctld process 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 spec.storage.runtime.objectEncryptionEnabled.
Direct File Operations#
For s0fs Volumes, you can operate on files 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 go through the normal gateway chain and team-scoped auth. When an s0fs volume is already mounted into a sandbox, the API request is served by that mounted owner. When it is not mounted, the storage runtime in manager lazily attaches the volume on demand and reclaims idle direct mounts later. s3 backend Volumes require an active mounted owner for direct file API requests.
The direct file API surface mirrors the Sandbox file API:
| Operation | Endpoint |
|---|---|
| Read / Write / Delete | GET, POST, DELETE /api/v1/sandboxvolumes/{'{id}'}/files?path=... |
| Stat | GET /api/v1/sandboxvolumes/{'{id}'}/files/stat?path=... |
| List directory | GET /api/v1/sandboxvolumes/{'{id}'}/files/list?path=... |
| Move / Rename | POST /api/v1/sandboxvolumes/{'{id}'}/files/move |
| Watch | GET /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.
/api/v1/sandboxvolumes
govolume, 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.
/api/v1/sandboxvolumes/{id}
govol, err := client.GetVolume(ctx, volume.ID) if err != nil { log.Fatal(err) } fmt.Printf("Volume: %s (backend: %s)\n", vol.ID, vol.Backend)
List Volumes#
List all volumes in the current team.
/api/v1/sandboxvolumes
govolumes, err := client.ListVolume(ctx) if err != nil { log.Fatal(err) } for _, v := range volumes { fmt.Printf("- %s (%s)\n", v.ID, v.Backend) }
Delete Volume#
Delete a volume when it is no longer needed.
/api/v1/sandboxvolumes/{id}
go_, err = client.DeleteVolume(ctx, volume.ID) if err != nil { log.Fatal(err) } fmt.Println("Volume deleted")
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.
Backends
Choose between s0fs and S3-compatible backend behavior.