Volumes
Persistent storage that sandboxes attach at creation and that outlives any single sandbox.
A volume is persistent, project-scoped storage that a sandbox mounts as a directory. Data on a volume stays when the sandbox is deleted, and a sandbox created later can attach the same volume and read it. A sandbox attaches up to 8 volumes, and several sandboxes can attach the same volume at once.
Daytona has two volume types. A disk volume is mounted as a local ext4 filesystem: the sandbox reads and writes a private copy of the volume at local disk speed, and Daytona commits its changes back to the volume in the background. A bucket volume is an S3 bucket mounted live inside the sandbox: every read and write goes to the bucket. Daytona creates and owns the bucket of a managed bucket volume, and an external bucket volume mounts a bucket you own with credentials stored as secrets.
Volumes are attached when a sandbox is created and stay attached until the sandbox is deleted. Linux sandboxes attach disk and bucket volumes, macOS sandboxes attach disk volumes, and Windows and GPU sandboxes do not attach volumes.
| Concept | Description |
|---|---|
| Disk volume | A tree of files stored by Daytona and mounted as ext4. Each sandbox writes a private copy that is committed back. |
| Bucket volume | An S3 bucket, or a key prefix in one, mounted live. Reads and writes go straight to the bucket. |
| Managed bucket | A bucket volume whose bucket Daytona creates and deletes. |
| External bucket | A bucket volume on a bucket you own, on AWS S3 or an S3-compatible service, accessed with keys stored as project secrets. |
| Mount | The attachment of a volume to a sandbox at an absolute path, optionally limited to a subpath and, for bucket volumes, read-only. |
| Commit | The background upload of a sandbox's changes on a disk volume. Other sandboxes see committed changes the next time they attach or pull. |
| Name | Unique in the project. 1 to 128 ASCII letters, digits, periods, underscores, and hyphens, not starting with a period or a reserved prefix. |
Use cases
Volumes are used when data must outlive a sandbox or be shared between sandboxes.
-
Results that outlive the sandbox. A sandbox writes its output to a volume, and the data stays after the sandbox is deleted. Deleting the sandbox runs a final commit of its disk volume changes. A sandbox created later attaches the same volume and reads the results once that commit is published, which can be seconds to minutes after the first sandbox reports deleted. Check that the results are present before the next sandbox depends on them.
-
Shared datasets and model weights. A dataset or a set of weights is written to a volume once, and every sandbox that needs it attaches the volume instead of downloading it again. Several sandboxes can attach the same volume at once. On a bucket volume, every sandbox that has the bucket mounted sees new changes. On a disk volume, give each writing sandbox its own files: a file that several sandboxes write keeps one sandbox's version.
-
Caches across runs. Package, build, and model caches on a volume carry over to the next sandbox, so it skips the downloads and builds the previous one already did. A new sandbox starts from the latest published commit of a disk volume. The sandbox reads and writes its cache in a private copy of the volume: the first read of a file fetches it from storage, and later reads and all writes run at local disk speed.
-
Data in your own bucket. An external bucket volume mounts a bucket you already use. Sandbox code reads and writes it as files, and the access keys never enter the sandbox. Deleting the volume leaves the bucket and its objects in place.
-
Read-only inputs. A bucket volume mounted read-only gives untrusted code access to input data that it cannot change or delete. Daytona enforces the read-only mount outside the sandbox, so even a process running as root cannot write to it. Only bucket volumes support read-only mounts.
Create volumes
Create a disk volume. A volume is ready as soon as the create returns, and its data starts empty. The size sets the capacity of the private copy each sandbox writes to, not a limit on the data the volume stores in total.
from daytona import Daytona
daytona = Daytona()
volume = daytona.v2.volumes.create(name="training-data", size_gib=200)
print(volume.id, volume.status)| Parameter | Type | Description |
|---|---|---|
name | str | Sets the volume name. This parameter is required. The name must be unique in the project. |
size_gib | int | Sets the capacity of each sandbox's copy of a disk volume, from 1 to 1000 GiB. The default is 100. |
type | str | Sets the volume type: disk or bucket. The default is disk. |
Managed bucket volumes
Create a bucket volume without bucket settings to have Daytona create a new, empty bucket for it. Daytona owns the bucket: deleting the volume deletes the bucket and every object in it. Traffic to a bucket volume is billed per uploaded GiB, downloaded GiB, and 1,000 requests; see billing.
from daytona import Daytona
daytona = Daytona()
volume = daytona.v2.volumes.create(name="shared-outputs", type="bucket")
print(volume.id, volume.status)External bucket volumes
Create a bucket volume on a bucket you own by passing its settings. Daytona stores only the settings: it does not create the bucket, and deleting the volume leaves the bucket and its objects in place. The bucket can be on AWS S3 or on an S3-compatible service. The SDKs do not create external bucket volumes.
The access key and secret key are read from two project secrets. The keys are used outside the sandbox: sandbox code reads and writes the bucket through Daytona and never receives the keys.
daytona volume create corp-data --type bucket --bucket corp-data --bucket-region us-east-1 \
--prefix datasets/ --access-key-secret AWS_KEY --secret-key-secret AWS_SECRET| Flag | Type | Description |
|---|---|---|
--bucket | string | Sets the bucket name. This flag is required for an external bucket volume; without it, Daytona creates a managed bucket. |
--bucket-region | string | Sets the S3 region of the bucket, not a Daytona region. Defaults to the default bucket region of the deployment. |
--endpoint | string | Sets the endpoint of an S3-compatible service as an http:// or https:// URL. Omit it for AWS S3. |
--prefix | string | Limits the volume to keys under this prefix. |
--path-style | bool | Sends requests with path-style addressing instead of virtual-hosted addressing. |
--access-key-secret | string | Sets the name of the project secret that holds the access key. This flag is required. |
--secret-key-secret | string | Sets the name of the project secret that holds the secret key. This flag is required. |
The rules for each value are the same as for the matching API field.
Attach volumes
Attach volumes to a sandbox by listing them when the sandbox is created. Each mount names a volume by name or ID and an absolute path inside the sandbox. A mount can be limited to a subpath: a directory of a disk volume, or a key prefix under a bucket volume's own prefix. A mount never reaches outside the volume.
Volumes cannot be attached to or detached from a running sandbox. To change the volumes of a sandbox, create a new sandbox with the volumes it needs.
The examples attach two disk volumes, training-data and home-dirs, and the external bucket volume corp-data. Create them first, as described in Create volumes and External bucket volumes.
from daytona import Daytona
from daytona.v2 import VolumeMount
daytona = Daytona()
sandbox = daytona.v2.create(
volumes=[
VolumeMount(volume_id="training-data", mount_path="/mnt/data"),
VolumeMount(volume_id="home-dirs", mount_path="/home/alice", subpath="users/alice"),
VolumeMount(volume_id="corp-data", mount_path="/mnt/corp", read_only=True),
],
)| Parameter | Type | Description |
|---|---|---|
volumes | Sequence[VolumeMount] | Sets the volumes to attach. At most 8. |
VolumeMount.volume_id | str | Sets the volume by name or by vol_ ID. Each volume can be attached once per sandbox. This parameter is required. |
VolumeMount.mount_path | str | Sets the absolute path of the mount, with no ... Repeated and trailing slashes are ignored, and each mount needs a different path. / and paths at or under /bin, /boot, /dev, /etc, /lib, /lib32, /lib64, /mnt/scratch, /overlay, /proc, /sbin, /sys, and /usr are reserved. This parameter is required. |
VolumeMount.subpath | str | Mounts only this relative path of the volume, with no empty or .. segments. |
VolumeMount.read_only | bool | Mounts the volume without write access. Supported only for bucket volumes. |
Disk volume data
A sandbox reads and writes a disk volume as a local ext4 filesystem. When the sandbox starts, Daytona builds a private copy of the volume's latest published commit for that sandbox. The first read of a file fetches it from storage, and later reads and all writes go to the private copy at local disk speed.
Daytona commits a sandbox's changes to the volume in the background. A commit runs at most every 5 seconds, and only once the sandbox has stopped writing to the volume for about 1 second. A sandbox that never pauses writing for 1 second does not commit until it is stopped or deleted. Run the sync command in the sandbox before you depend on a recent write being in the next commit.
Stopping or deleting the sandbox runs a final commit. The sandbox can report stopped or deleted before that commit is published, which takes from a few seconds to several minutes for large changes. Until then, a sandbox that attaches the volume sees the earlier contents, and the stopped sandbox cannot be started again. Check that the files you need are present before a new sandbox depends on them.
A sandbox sees other sandboxes' commits when it attaches the volume, which happens when the sandbox is created or started. Changes another sandbox commits while this sandbox runs are not visible to it until it pulls the volume.
A commit stores file contents, directories, symbolic links, hard links, permission modes, and modification times. It does not store file owners, named pipes, sockets, device nodes, or extended attributes: files come back owned by root. Holes in sparse files are stored as zeros, so a sparse file takes its full size in each sandbox's copy and in the volume's reported size.
When several sandboxes change the same volume, each commit merges its changes into the latest contents of the volume instead of replacing them:
| Change | Result |
|---|---|
| A path changed by only one sandbox | That sandbox's version is kept. |
| A path changed by two sandboxes | The version with the newer modification time is kept. |
| A path modified by one sandbox, deleted by another | The modified version is kept. |
| A path that is a file in one sandbox and a directory in another | The directory is kept. |
The merge compares file modification times set by the sandboxes. A tool that preserves old modification times, such as an archive extraction, can lose to an older edit made in another sandbox. The version that loses is discarded and cannot be recovered.
Each path is merged on its own. Files that must stay consistent with each other, such as a database and its write-ahead log or a Git object store, can end up combined from different sandboxes. Write them from one sandbox only, and have other sandboxes read them or write their own files.
Pull volume changes
Pull a disk volume to bring the changes other sandboxes committed into a running sandbox, without restarting it. If the volume has new commits, the pull first commits this sandbox's own changes, merges them as a background commit does, and then applies the merged contents to the sandbox's copy. If the volume has no new commits, the pull returns without committing local changes. A pull can outlast its wait: a pending result means the pull has no result yet, and repeating the request with the same request ID returns its result. Bucket volumes need no pull, because every read goes to the bucket.
A pull stages incoming changes in a .rlp-sync directory at the root of the mount. Daytona does not commit this directory, and a pull fails when the name is taken by a file or by a directory Daytona did not create.
from daytona import Daytona
from daytona.v2 import VolumeMount
daytona = Daytona()
sandbox = daytona.v2.create(volumes=[VolumeMount(volume_id="training-data", mount_path="/mnt/data")])
volume = daytona.v2.volumes.get("training-data")
result = sandbox.volumes.pull(volume.id, request_id="refresh-42", timeout=10)
while result.status == "pending":
result = sandbox.volumes.pull(volume.id, request_id="refresh-42", timeout=10)
print(result.status, result.manifest_id)| Parameter | Type | Description |
|---|---|---|
volume_id | str | Sets the vol_ ID of a volume attached to the sandbox. This is the first positional argument. |
request_id | str | Sets the identity of the pull: 1 to 128 ASCII letters, digits, underscores, and hyphens. Reuse it to retry the pull or read its result. This parameter is required. |
timeout | float | Sets how many seconds the server waits for the pull, from 0 to 30. 0 submits the pull, or returns the last result of a known request ID without waiting or retrying it. A wait above 0 retries a pull that is pending, unavailable (503), or refused by the rate limit. The default is 30. |
The result has the following fields:
| Field | Description |
|---|---|
status | pulled when the sandbox received new changes, unchanged when the volume had none, pending when the pull has no result within the wait, or unknown when the SDK does not recognize the status the server returns. |
raw_status | The status exactly as the server returns it. Read it when status is unknown. |
manifest_id | The version of the volume that the pull applied or found. None while the pull is pending. |
Bucket volume data
A sandbox mounts a bucket volume with Mountpoint for Amazon S3. Each read and write is a request to the bucket, and closing a written file returns only after the object is stored, or with an error if the upload fails. Changes are visible to every sandbox that has the bucket mounted and to any other client of the bucket.
Files on a bucket volume are S3 objects, so file system operations that S3 does not support are not available. A file is written sequentially from the start, an existing file can be replaced but not modified in place, and symbolic links, hard links, and permission changes are not supported. Every file operation is a round trip to the bucket, so workloads with many small files run slower than on a disk volume.
A read-only mount is enforced outside the sandbox. A process in the sandbox, including one running as root, cannot write to or delete from a read-only mount.
Operations
List volumes
List the volumes of the project, newest first. The list returns one page at a time, and each page holds the cursor of the next one. Volumes that are being deleted are listed with the deleting status, and deleted volumes are not listed.
from daytona import Daytona
daytona = Daytona()
cursor = None
while True:
page = daytona.v2.volumes.list(limit=50, cursor=cursor)
for v in page.items:
print(v.id, v.name, v.type, v.status)
cursor = page.next_cursor # None on the last page
if cursor is None:
break| Parameter | Type | Description |
|---|---|---|
limit | int | Sets the page size, from 1 to 200. The default is 100. |
cursor | str | Resumes the list after the previous page. Pass the next_cursor of that page. Omit it for the first page. |
Iterate over all volumes
Iterate over every volume of the project without handling page cursors. The iterator fetches the next page when the current one is exhausted and stops when the server returns no cursor.
from daytona import Daytona
daytona = Daytona()
for v in daytona.v2.volumes.iter():
print(v.id, v.name, v.type, v.status)Get a volume
Get a volume by name or ID. The response holds the volume's type, status, size, and options. For a disk volume, it also reports the size of the volume's files at the last commit and when that size was observed; both are empty until the first commit. Bucket volume options include the bucket settings and the names of the credential secrets, never the keys.
from daytona import Daytona
daytona = Daytona()
volume = daytona.v2.volumes.get("training-data")
print(volume.type, volume.status, volume.size_gib, volume.used_bytes, volume.used_bytes_at)
# Create the disk volume if it does not exist
volume = daytona.v2.volumes.get("scratch-cache", create=True)| Parameter | Type | Description |
|---|---|---|
name_or_id | str | Sets the volume by name or by vol_ ID. This is the first positional argument. |
create | bool | Creates a disk volume with this name and the default size when none exists. If another caller creates it first, the call fails; repeat it. Ignored with id_only. Defaults to False. |
id_only | bool | Resolves the value only as an ID, without falling back to a name. Defaults to False. |
Delete volumes
Delete a volume by name or ID. Deletion runs in the background: the volume moves to the deleting status and then to deleted. Deleting a disk volume deletes its data, and deleting a managed bucket volume deletes the bucket and every object in it. Deleting an external bucket volume leaves the bucket and its objects in place.
from daytona import Daytona
daytona = Daytona()
daytona.v2.volumes.delete("training-data")| Parameter | Type | Description |
|---|---|---|
name_or_id | str | Sets the volume by name or by vol_ ID. This is the first positional argument. |
id_only | bool | Resolves the value only as an ID, without falling back to a name. The default is False. |
Permissions
Volumes are scoped to a project. A volume in one project cannot be attached to a sandbox in another. Access is governed by three permissions, which you can grant to an organization role or an API key.
Creating an external bucket volume also requires permission to read secrets, because the volume references project secrets. Pulling a volume runs through the toolbox of the sandbox and requires the sandbox write permission. The audit logs record volume creates and deletes.
| Permission | Grants |
|---|---|
read:volumes | List and get volumes |
write:volumes | Create volumes |
delete:volumes | Delete volumes |