# Volumes A **persistent volume** is a named disk that survives a sandbox and follows you to the next one. Attach it and it mounts at `/persist` - anything you write there is still there when a later sandbox attaches the same volume. It's how you keep data (caches, model weights, a working directory, a database file) across otherwise-ephemeral sandboxes. > **The short version** > > - `Sandbox.create(volume="my-data")` mounts the volume at `/persist`. > - Write under `/persist` - it persists across terminate. > - Re-use the same volume name in a later sandbox to get the data back. > - Created on first use (default 10 GB); one running sandbox per volume at a time. ## /persist vs /workspace A sandbox has two writable places, and the difference is the whole point: - `/workspace` (and `/tmp`) - fast scratch, but **RAM-backed**. Gone the moment the sandbox terminates, and it counts against the sandbox's memory. - `/persist` - a real disk (ext4) backed by a volume. **Survives terminate** and re-attaches to a later sandbox. Only present when you attach a volume. So: use `/workspace` for throwaway working files, and `/persist` for anything you want to keep. ## Attach a volume Name a volume when you create a sandbox. If it doesn't exist yet it's created on first reference (default 10 GB), so you rarely need to make one up front. **Python:** ```python from orkestr import Sandbox # Name a volume in create() and it's attached at /persist. It's created on # first use (default 10 GB), so you don't have to make it up front. sbx = Sandbox.create(template="python-3.12", volume="my-data") sbx.exec("echo 'trained weights v1' > /persist/model.txt") sbx.terminate() # Hours or days later, a brand-new sandbox re-attaches the same volume: sbx = Sandbox.create(template="python-3.12", volume="my-data") print(sbx.exec("cat /persist/model.txt").stdout) # -> trained weights v1 print(sbx.volume) # -> "my-data" ``` **JavaScript:** ```javascript import { Sandbox } from "orkestr"; // Name a volume in create() and it's attached at /persist. Created on first // use (default 10 GB). let sbx = await Sandbox.create({ template: "python-3.12", volume: "my-data" }); await sbx.exec("echo 'trained weights v1' > /persist/model.txt"); await sbx.terminate(); // A brand-new sandbox re-attaches the same volume and sees the data: sbx = await Sandbox.create({ template: "python-3.12", volume: "my-data" }); console.log((await sbx.exec("cat /persist/model.txt")).stdout); // trained weights v1 console.log(sbx.volume); // "my-data" ``` **cURL:** ```bash # Attach a volume by name (or vol_ id) when you create the sandbox. curl -X POST https://api.orkestr.eu/v1/sandboxes \ -H "Authorization: Bearer $ORKESTR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template": "python-3.12", "volume": "my-data" }' # => { "id": "sbx_...", "volume": "my-data", ... } ``` ## Manage volumes explicitly You only need this to set a custom size, list what you have, or delete one - attaching by name already creates it. **Python:** ```python from orkestr import Volume # Explicit control - custom size, a region pin, listing, cleanup: vol = Volume.create("datasets", size_gb=50, region="rbx") for v in Volume.list(): print(v.name, v.size_mb, v.status, v.region) # status: ready | attached Volume.move(vol.id, "hel1") # re-home a detached volume to another region Volume.delete(vol.id) # rejected while attached to a running sandbox ``` **JavaScript:** ```javascript import { Volume } from "orkestr"; const vol = await Volume.create("datasets", { sizeGb: 50, region: "rbx" }); for (const v of await Volume.list()) console.log(v.name, v.sizeMb, v.status, v.region); await Volume.move(vol.id, "hel1"); // re-home a detached volume to another region await Volume.delete(vol.id); // rejected while attached to a running sandbox ``` **cURL:** ```bash # Create (name unique per account; size_gb is the provisioned ceiling; # region pins where it lives - omit it to follow the first sandbox). curl -X POST https://api.orkestr.eu/v1/sandboxes/volumes \ -H "Authorization: Bearer $ORKESTR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "datasets", "size_gb": 50, "region": "rbx" }' # List: curl https://api.orkestr.eu/v1/sandboxes/volumes \ -H "Authorization: Bearer $ORKESTR_API_KEY" # Move a detached volume to another region: curl -X POST https://api.orkestr.eu/v1/sandboxes/volumes/vol_01J.../move \ -H "Authorization: Bearer $ORKESTR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "region": "hel1" }' # Delete (rejected while attached to a running sandbox): curl -X DELETE https://api.orkestr.eu/v1/sandboxes/volumes/vol_01J... \ -H "Authorization: Bearer $ORKESTR_API_KEY" ``` ## How it works ### One writer at a time A volume can back **only one running (or paused) sandbox at a time**. Attaching a volume that's already in use returns a `409 volume_in_use` - terminate the holder first. This is deliberate: an ext4 filesystem mounted read-write by two VMs at once would corrupt. When a sandbox terminates (or expires), its volume is released back to *available* and you can re-attach it. ### Durability & where the data lives The volume's live data is a disk on the sandbox host (fast, local). On detach and on a periodic timer, it's also **checkpointed to EU object storage** - so the data is durable against a host failure, and a later sandbox can pick the volume up even on a different host (it hydrates from the checkpoint). A running sandbox reads and writes the local disk directly; the checkpoint happens out of band. > **Consistency** > > A same-box reattach always sees your latest writes - it reads the volume's disk directly. On terminate the volume is released right away and its durable copy is refreshed a moment later (out of band); otherwise the durable copy is as fresh as the last periodic checkpoint. For a working cache or checkpointed results this is exactly what you want; don't treat a volume as a transactional database's only home. ### Size & pricing `size_gb` is the **provisioned** capacity (default 10, max 100). Storage bills as **GB-month at that provisioned size** for as long as the volume exists, whether or not it's full - so request what you need, not the max. It shares the same storage meter, free allowance, and pay-as-you-go invoice as paused-sandbox snapshots and custom templates. Each plan caps the number of volumes and the total provisioned GB; call `get_sandbox_limits` (or `GET /v1/sandboxes/limits`) to see `max_volumes` / `max_volume_gb` for your tier. ### Region & placement A volume lives in one region, and a sandbox attaching it runs there too. You choose the region in either direction: pass `region` when you create the volume explicitly, or just create your first sandbox with a `region` and name a new volume - the volume homes wherever that sandbox lands. After that, attaching the volume places the sandbox in its region; passing a *different* `region` on create is rejected with a clear error rather than silently moving your sandbox. To relocate a volume, move it while detached (`Volume.move` / `POST /v1/sandboxes/volumes/{id}/move`): the data is checkpointed to object storage and the volume re-materializes in the target region on its next attach. And if the volume's host is ever unavailable, the control plane hydrates it onto another host from the checkpoint automatically. ## Console Volumes show up on the [Sandboxes page](https://orkestr.eu/sandboxes) under **Volumes** - each with a `mounted` / `available` state - and on a sandbox's detail page next to its resources. You can create and delete them there, and pick one to attach when you create a sandbox from the console. ## Next steps - [Python SDK - volumes](https://orkestr.eu/docs/sandboxes/python-sdk#volumes) - [JS SDK - volumes](https://orkestr.eu/docs/sandboxes/js-sdk#volumes) - [REST API reference](https://orkestr.eu/docs/sandboxes/api-reference) - [MCP server](https://orkestr.eu/docs/sandboxes/mcp) - `create_volume` / `list_volumes` / `move_volume` / `delete_volume`