Volumes
Shared file access across sandboxes.
Volumes are named, project-scoped storage that persists independently of any sandbox. Sandboxes come and go; a volume persists, and any number of sandboxes can attach it, one after another or several at the same time. Use volumes for datasets, model weights, package caches, build artifacts, and per-tenant data.
Volumes make a sandbox's data durable and shared; a persistent sandbox makes the whole machine durable, and it belongs to exactly one sandbox.
| Concept | Description |
|---|---|
| Volume | A named, project-scoped directory tree that exists independently of any sandbox. Created once with a type, attached to any number of sandboxes. |
| Mount | A volume attached to one sandbox at an absolute path, optionally scoped to a subpath and, for bucket volumes, mounted read-only. A sandbox can have up to 8 volume mounts. |
| Commit | Disk volumes only. The background sync from a sandbox's local copy back to the volume. Once committed, data survives sandbox teardown and host failure. |
Use cases
Volumes are used when data must outlive a sandbox or be shared between sandboxes.
- Datasets and model weights: large files are stored once and attached to every sandbox that reads them, instead of downloaded at each boot.
- Package and build caches: dependency caches and build outputs on a volume carry over from one ephemeral sandbox to the next.
- Handoff between sandboxes: a producer sandbox writes results to a disk volume, and a consumer sandbox attaches the volume after the commit.
- Shared reference data: a bucket volume mounted read-only gives many sandboxes the same data, and none of them can change it.
- Per-tenant data: each tenant's data lives under its own subpath, and a sandbox mounts only the subpath of its tenant.
- Data in your own bucket: a volume on a bucket you own exposes its existing objects to sandboxes, and the bucket stays under your control.
- Live sharing between running sandboxes: sandboxes that run at the same time mount one bucket volume. A file that one sandbox closes is visible to the others within seconds.
- Results from ephemeral sandboxes: an ephemeral or spot sandbox writes its results to a volume. The results survive the deletion or the reclaim of the sandbox.
Create volumes
Daytona provides two volume types:
- Disk volume gives each sandbox a fast local copy, merged back on commit.
- Bucket volume mounts an S3 bucket live inside the sandbox, so running sandboxes see each other's files within seconds and you can point a volume at a bucket you already own.
- Go to Daytona Volumes ↗
- Click Create Volume
- Enter a Name and select a Type
- For a disk volume, set Size (GiB)
- For a bucket volume, leave the fields empty for a platform-owned bucket, or enter your Bucket name, Region, Endpoint, Prefix, and the Access key and Secret key secret names
- Click Create
Disk volumes
A disk volume (the default type) is a private local disk per sandbox. The sandbox does not perform remote I/O on every read and write. It works on a fast, private local copy of the volume at local-disk speed, and changes commit back to the volume in the background. Committed data survives sandbox deletion and becomes visible to other sandboxes that attach the same volume after the commit landed. A running sandbox never sees another sandbox's writes.
from daytona import Daytona
daytona = Daytona()
volume = daytona.v2.volume.create(name="training-data")| Parameter | Type | Description |
|---|---|---|
name | str | Sets the volume name. The name is 1 to 128 characters of letters, digits, ., _, and -. It must not start with . or with a reserved prefix: snap-, build-, refs, by-digest, or prj-. Names are unique within a project. This parameter is keyword-only and required. |
size_gb | int | Sets the disk volume size in GiB, from 1 to 10000. This parameter is keyword-only. The default is 100. |
Bucket volumes
A bucket volume mounts an S3 bucket live inside the sandbox. Every mounted sandbox reads and writes the same objects, and a file one sandbox closes becomes visible to the others within seconds. The bucket has one of two origins:
- Platform-owned: omit the bucket block, and Daytona creates and owns the bucket.
- External: pass a bucket block naming a bucket you own, with credentials as the names of two project secrets; their values never enter the API.
daytona volume create llama-weights --type bucketTo point the volume at a bucket you own, add a bucket block. See bring your own bucket.
Bring your own bucket
Create a bucket volume on a bucket you own. Daytona stores the bucket location and the names of two project secrets that hold the access key and the secret key. It does not contact the bucket when it creates the volume, and it never creates or deletes your bucket.
Both secrets must exist in the project before you create the volume.
daytona volume create corp-data \
--type bucket \
--bucket corp-data \
--region us-east-1 \
--access-key-secret AWS_KEY \
--secret-key-secret AWS_SECRET| Field | Description |
|---|---|
name | Bucket name, 3 to 63 characters of [a-z0-9.-]. Required. |
credentials | access_key_secret and secret_key_secret, the names of two project secrets. Required. |
region | Bucket region. Defaults to the deployment's default bucket region. |
endpoint | Non-AWS S3 endpoint as an http(s):// URL, for R2, MinIO, and similar. Absent means AWS. |
prefix | Key prefix the volume is scoped to. A trailing / is added if missing. Empty means the whole bucket. |
path_style | Use path-style addressing. Defaults to false. |
Mount volumes
Mount volumes when creating a sandbox.
from daytona import Daytona
from daytona.v2 import VolumeMount
daytona = Daytona()
volume = daytona.v2.volume.get("my-awesome-volume", create=True)
sandbox = daytona.v2.create(
volumes=[VolumeMount(volume_id=volume.id, mount_path="/data")],
)| Parameter | Type | Description |
|---|---|---|
create | bool | Creates a disk volume with the default size if no volume has that name. If it is False, a missing volume raises NotFoundError. The default is False. |
Mount with subpath
Mount part of a volume. The sandbox sees only that subtree: a disk volume subdirectory, or a key prefix under a bucket volume's prefix. Several sandboxes can mount different subtrees.
from daytona import Daytona
from daytona.v2 import VolumeMount
daytona = Daytona()
volume = daytona.v2.volume.get("my-awesome-volume", create=True)
# Each sandbox mounts only its tenant's slice of the shared volume
sandbox = daytona.v2.create(
volumes=[VolumeMount(
volume_id=volume.id,
mount_path="/data",
subpath="tenants/tenant-a",
)],
)Mount read-only
Mount a bucket volume without write access. Read-only mounts apply to bucket volumes only; a read-only mount of a disk volume fails the sandbox create.
from daytona import Daytona
from daytona.v2 import VolumeMount
daytona = Daytona()
volume = daytona.v2.volume.get("llama-weights")
sandbox = daytona.v2.create(
volumes=[VolumeMount(volume_id=volume.id, mount_path="/models", read_only=True)],
)Share data between sandboxes
Share through a bucket volume
Share files between sandboxes that run at the same time. Both sandboxes mount the same bucket volume, and a file the producer closes becomes visible to the consumer within the directory listing cache window of a few seconds.
import time
from daytona import Daytona
from daytona.v2 import VolumeMount
daytona = Daytona()
# Created with `POST /volumes {"name": "rollouts", "type": "bucket"}`
volume = daytona.v2.volume.get("rollouts")
producer = daytona.v2.create(
volumes=[VolumeMount(volume_id=volume.id, mount_path="/out")],
)
consumer = daytona.v2.create(
volumes=[VolumeMount(volume_id=volume.id, mount_path="/in", read_only=True)],
)
producer.process.run("mkdir -p /out/results && echo done > /out/results/done.txt")
# Closed files reach the consumer within the listing cache window, so poll
for _ in range(30):
response = consumer.process.run("ls /in/results")
if response.exit_code == 0:
break
time.sleep(1)
print(response.output)Share through a disk volume
Hand data from one sandbox to the next. A running sandbox never sees another sandbox's writes, so the consumer attaches the disk volume after the producer's changes are committed. Stopping or deleting the producer runs a final commit; the consumer waits until the volume reports a new commit before it attaches.
import time
from daytona import Daytona
from daytona.v2 import VolumeMount
daytona = Daytona()
volume = daytona.v2.volume.get("results", create=True)
# Producer sandbox writes results onto the volume
producer = daytona.v2.create(
volumes=[VolumeMount(volume_id=volume.id, mount_path="/data")],
)
# Record the commit pointer before writing, so any later commit changes it
before = daytona.v2.volume.get(volume.id).last_manifest_id
producer.process.run("mkdir -p /data/results && echo done > /data/results/done.txt && sync")
# Deleting the writer triggers its final commit
producer.delete()
# Wait for the commit to land before the next sandbox attaches
for _ in range(30):
if daytona.v2.volume.get(volume.id).last_manifest_id != before:
break
time.sleep(1)
consumer = daytona.v2.create(
volumes=[VolumeMount(volume_id=volume.id, mount_path="/data")],
)
response = consumer.process.run("ls /data/results")
print(response.output)Operations
List volumes
- Go to Daytona Dashboard ↗
- Filter the list by state or bucket origin
from daytona import Daytona
daytona = Daytona()
page = daytona.v2.volume.list()
for volume in page.items:
print(volume.name, volume.type, volume.state)Iterate over all volumes
from daytona import Daytona
daytona = Daytona()
for volume in daytona.v2.volume.iter():
print(volume.name, volume.type, volume.state)Get a volume
Get a volume by ID or name. A reference that matches one volume's ID and another volume's name is ambiguous; resolve it as an ID only to avoid this. The detail response adds commit information for disk volumes, read from the volume store on a best-effort basis.
| Field | Description |
|---|---|
size_gb | Capacity of the per-sandbox local copy. Disk volumes only; null for a bucket volume. |
options | Per-type options: {} for a disk volume, {"bucket": {...}} for a bucket volume. Credentials appear as secret names, never values. |
used_bytes | Logical size of the last committed tree, in bytes. Disk volumes only; absent until the first commit. |
used_bytes_at | When used_bytes was observed. |
last_manifest_id | Pointer to the latest commit. Disk volumes only; read best-effort from the store, detail routes only. |
conflicts | Concurrent-write conflicts recorded on the latest commit. Disk volumes only. |
last_used_at | When a sandbox last attached the volume. |
error_reason | Why the volume is in state error, if it is. |
from daytona import Daytona
daytona = Daytona()
volume = daytona.v2.volume.get("my-awesome-volume")| Parameter | Type | Description |
|---|---|---|
id_only | bool | Resolves the argument as an ID only, if True. The default is False. |
Delete volumes
Delete a volume. Daytona refuses to delete a volume a sandbox still attaches: delete the sandbox and wait for its teardown first. Deleting a platform-owned bucket volume also deletes its bucket and all its objects; deleting a volume on your own bucket leaves it untouched.
- Go to Daytona Dashboard ↗
- Click the ⋮ menu of the volume and click Delete
- Click Delete Volume to confirm
from daytona import Daytona
daytona = Daytona()
daytona.v2.volume.delete("my-awesome-volume")