Sandbox

Persistent Volumes

Use Tenki Sandbox persistent volumes for durable build caches, package caches, large repositories, and datasets that survive session termination.

Persistent volumes are workspace-scoped block storage that survives the session it was used in. Volumes are the right tool for durable data: package caches, build caches, large repos, datasets, anything you want available across many sessions.

Volume lifecycle

Pick your tool, and the selection syncs across the rest of the page.

Create

tenki sandbox volume create --name npm-cache --size 20GB

The CLI requires explicit units:

  • 10GB, 10GiB, 500MB: accepted
  • bare 1024: rejected
import { GiB } from "@tenkicloud/sandbox";

const volume = await sandbox.createVolume({
  name: "data-vol",
  sizeBytes: 10 * GiB,
});

await sandbox.waitVolumeReady(volume.id);
volume, err := client.CreateVolume(
  ctx,
  tenkisandbox.WithVolumeName("npm-cache"),
  tenkisandbox.WithVolumeSize(20*tenkisandbox.GB),
)

volume, err = client.WaitVolumeReady(ctx, volume.ID, 3*time.Minute)

Useful size constants: tenkisandbox.KB, MB, GB (and KiB, MiB, GiB for binary units).

from tenki import Client, GiB

client = Client()

volume = client.volumes.create(
    name="npm-cache",
    size_bytes=20 * GiB,
)

volume = client.volumes.wait_ready(volume.id, timeout=180)

Size constants are exported at the top level: KB, MB, GB (and KiB, MiB, GiB for binary units).

Volumes can be between 1 MiB and 100 GiB.

List, get, resize, delete

const volumes = await sandbox.listVolumes();
const volume = await sandbox.getVolume(volumeId);
await sandbox.resizeVolume(volumeId, 40 * GiB);
await sandbox.deleteVolume(volumeId);
volumes = client.volumes.list()
volume = client.volumes.get(volume_id)
volume = client.volumes.resize(volume_id, 40 * GB)
client.volumes.delete(volume_id)
volumes, err := client.ListVolumes(ctx)
volume, err := client.GetVolume(ctx, volumeID)
volume, err = client.ResizeVolume(ctx, volumeID, 40*tenkisandbox.GB)
err = client.DeleteVolume(ctx, volumeID)
tenki sandbox volume list
tenki sandbox volume list --json
tenki sandbox volume get <volume-id>
tenki sandbox volume resize <volume-id> --size 40GB
tenki sandbox volume delete <volume-id>

Attach to sessions

Attach at create time

tenki sandbox create \
  --volume <volume-id>:/workspace/cache \
  --volume <other-id>:/workspace/reference:ro

The optional :ro suffix mounts the volume read-only.

const session = await sandbox.createAndWait({
  volumes: [
    { volumeId: cacheVolumeId, mountPath: "/workspace/cache" },
    { volumeId: refVolumeId, mountPath: "/workspace/reference", readOnly: true },
  ],
});
session, err := client.Create(
  ctx,
  tenkisandbox.WithVolume(cacheVolumeID, "/workspace/cache"),
  tenkisandbox.WithVolume(refVolumeID, "/workspace/reference", tenkisandbox.WithReadOnly()),
)
sb = Sandbox.create(
    volumes=[
        {"volume_id": cache_volume_id, "mount_path": "/workspace/cache"},
        {"volume_id": ref_volume_id, "mount_path": "/workspace/reference", "read_only": True},
    ],
)

Attach to a running session

tenki sandbox volume attach <session-id> <volume-id> --mount /workspace/cache
tenki sandbox volume attach <session-id> <volume-id> --mount /workspace/reference --readonly
tenki sandbox volume detach <session-id> <volume-id>
await session.attachVolume(volume.id, "/mnt/data");
await session.detachVolume(volume.id);
err := session.AttachVolume(ctx, volumeID, "/workspace/cache")
err = session.AttachVolume(ctx, refVolumeID, "/workspace/reference", tenkisandbox.WithReadOnly())
err = session.DetachVolume(ctx, volumeID)
sb.attach_volume(volume_id, "/workspace/cache")
sb.attach_volume(ref_volume_id, "/workspace/reference", read_only=True)
sb.detach_volume(volume_id)

Workspace scope

Volumes belong to the workspace bound to your API key. Create and list calls infer that workspace, so do not pass a workspace ID.

Snapshots and volumes

Snapshots do not auto-reattach a session's volumes. A restored session is a normal session, so reattach any volume explicitly, at create time or with volume attach. See Snapshots for the restore workflow.

Keep durable data in volumes, not snapshots: baking a cache or dataset into a snapshot bloats it and ties that data to one VM's history.

Errors to handle

Volume calls can fail with a not-found, in-use, or quota error. The one worth handling is in-use: the volume is still attached to another session, so detach it from that session or wait, then retry. Every SDK surfaces these as typed errors or exceptions; see the SDK reference for the names in each language.