Volumes
A volume is durable storage that outlives any single machine. Create one once, then attach it to a sandbox at start-up and read and write it at a path of your choosing. When the machine goes away, the data stays. (Not yet available on desktops — see the callout below.)
Use a volume for anything you want to keep across runs and share between machines: datasets and model weights, a package cache, build artifacts, scraped output. Unlike a snapshot, which freezes a whole machine at a moment in time, a volume is a live folder you mount and keep writing to.
Volumes work today on sandboxes, in every isolation mode (shared, dedicated, and hardened). They are not yet available on desktops — that host fleet doesn’t carry the mount build yet. The volume itself mounts host-side through gcsfuse, using a per-volume downscoped credential minted for that mount, not the shared instance-wide credential a machine otherwise runs with.
A create that asks for volumes on a desktop, or on a sandbox pool that hasn’t picked up the mount build yet, is refused with a 409 HostCapabilityMissing rather than silently handed back with nothing mounted — see the errors page.
Create a volume
Volumes live on the volumes namespace of a SandboxClient (or DesktopClient). Creating one gives you back a volumeId you can attach later. A volume starts empty and belongs to your organization.
import { SandboxClient } from "@solarisdk/sandbox"
const sandboxes = new SandboxClient({
apiKey: process.env.SOLARI_API_KEY!,
baseUrl: "https://api.getsolari.com",
})
const vol = await sandboxes.volumes.create({
name: "datasets",
sizeMb: 4096, // optional soft size hint
metadata: { team: "ml" }, // optional free-form tags
})
console.log(vol.volumeId) // vol_0d10088366248dc85ee1489c9a96b1ccimport os
from solari_sandbox import SyncSandboxClient
sandboxes = SyncSandboxClient(api_key=os.environ["SOLARI_API_KEY"], base_url="https://api.getsolari.com")
vol = sandboxes.volumes.create(name="datasets", size_mb=4096, metadata={"team": "ml"})
print(vol["volumeId"])List, fetch, and delete
The client mirrors the same four calls in every SDK. Deleting a volume is idempotent: deleting one that is already gone still succeeds.
const all = await sandboxes.volumes.list() // every volume in your org
const one = await sandboxes.volumes.get(vol.volumeId)
await sandboxes.volumes.delete(vol.volumeId) // idempotentall_ = sandboxes.volumes.list()
one = sandboxes.volumes.get(vol["volumeId"])
sandboxes.volumes.delete(vol["volumeId"])Attach a volume to a sandbox
Pass volumes when you create a sandbox. Each entry maps a volumeId to a mount path inside the machine. The path appears as an ordinary folder. Read and write it like any other directory.
The path shape depends on isolation mode: on isolation:"dedicated" or "hardened" it must be absolute (/data). On isolation:"shared" it must instead be a short name — letters, digits, _ and - only, no slashes — because a shared sandbox’s filesystem is namespaced per context; the gateway mounts it under that context’s own root. Mixing the two up is a 400 naming the field.
// dedicated / hardened: absolute path
const sbx = await sandboxes.create({
template: "base",
volumes: [{ volumeId: vol.volumeId, path: "/data" }],
})
await sbx.connect()
// Everything under /data persists on the volume, not the sandbox.
await sbx.files.write("/data/results.json", JSON.stringify(output))
// isolation: "shared": short name, not a path
const shared = await sandboxes.create({
template: "base",
isolation: "shared",
volumes: [{ volumeId: vol.volumeId, path: "datasets" }],
})sbx = sandboxes.create(
template="base",
volumes=[{"volumeId": vol["volumeId"], "path": "/data"}],
)Two volumes cannot share the same mount path (or name, on shared) in one machine. A machine can mount several volumes at once, but each volume is single-attach — mounting one that is already attached to a different machine returns a 409. Detach it (stop or release the other machine) before mounting it elsewhere.
