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

Create

tenki sandbox volume create --workspace <workspace-id> --name npm-cache --size 20GB

The CLI requires explicit units:

  • 10GB, 10GiB, 500MB: accepted
  • bare 1024: rejected

Current public API validation:

  • minimum: 1 MiB
  • maximum: 100 GiB

List, get, resize, delete

tenki sandbox volume list --workspace <workspace-id>
tenki sandbox volume list --workspace <workspace-id> --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.

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>

Go SDK

volume, err := client.CreateVolume(
  ctx,
  tenkisandbox.WithWorkspaceID(workspaceID),
  tenkisandbox.WithVolumeName("npm-cache"),
  tenkisandbox.WithVolumeSize(20*tenkisandbox.GB),
)

volume, err = client.WaitVolumeReady(ctx, volume.ID, 3*time.Minute)
volumes, err := client.ListVolumes(ctx, workspaceID)
volume, err = client.GetVolume(ctx, volume.ID)
volume, err = client.ResizeVolume(ctx, volume.ID, 40*tenkisandbox.GB)

err = session.AttachVolume(ctx, volume.ID, "/workspace/cache")
err = session.AttachVolume(ctx, volume.ID, "/workspace/reference", tenkisandbox.WithReadOnly())
err = session.DetachVolume(ctx, volume.ID)

err = client.DeleteVolume(ctx, volume.ID)

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

TypeScript SDK

import { GiB } from "@tenki/sandbox";

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

await session.attachVolume(volume.id, "/mnt/data");
await session.detachVolume(volume.id);

Workspace ID lookup

Volumes are workspace-scoped, so you'll need a workspace ID. Use WhoAmI to discover them:

identity, err := client.WhoAmI(ctx)
workspaceID := identity.Workspaces[0].ID

See Identity discovery for the full call.

Snapshots, templates, and volumes

This is a critical contract:

  • Snapshots and templates do not auto-reattach the original session's volumes.
  • Restored sessions are normal sessions.
  • If you need a volume after restore, attach it explicitly on create or with volume attach.

Volumes vs. snapshots

Volumes are for durable data. Snapshots and templates are for durable VM state. Don't confuse them. Keeping cache data in a snapshot bloats the snapshot and ties the cache to one VM history.

Patterns

Persistent dev cache

tenki sandbox volume create --workspace <workspace-id> --name pnpm-cache --size 20GB
tenki sandbox create --name dev --volume <volume-id>:/workspace/.pnpm-store
tenki sandbox exec --session <session-id> bash -lc 'pnpm install'

A later session reuses the same cache:

tenki sandbox create --name dev-2 --volume <volume-id>:/workspace/.pnpm-store

Read-only reference dataset

tenki sandbox volume create --workspace <workspace-id> --name fixtures --size 10GB
tenki sandbox create --volume <volume-id>:/mnt/fixtures:ro

Mount the same dataset read-only into many concurrent sessions without copy overhead.

Errors to handle

The Go SDK maps service errors to typed values:

  • ErrVolumeNotFound
  • ErrVolumeInUse
  • ErrVolumeLimitExceeded

Pattern:

if errors.Is(err, tenkisandbox.ErrVolumeInUse) {
  // detach from the holding session, or wait
}
LinkedInProduct Hunt