Volumes
Mount object storage (S3, R2, GCS, Azure Blob, S3-compatible) into sandboxes. Define a volume once, attach it to many sandboxes.
A volume is a reusable, named definition of an object-storage location plus its credentials — much like a template. You create it once, then attach it to as many sandboxes as you like, each at its own mount path. Credentials are encrypted at rest and never returned by the API.
Create a reusable volume
import qbox
vol = qbox.Volumes.create(qbox.S3Volume(
name="data", # lowercase, used in env names: QBOX_VOLUME_data_*
bucket="my-bucket",
region="us-east-1",
access_key_id="AKIA...",
secret_access_key="...",
))
Typed builders exist for every provider: S3Volume, R2Volume,
S3CompatibleVolume, GCSVolume, AzureBlobVolume. Each takes the fields its
provider needs (bucket, account id, service-account JSON, etc.).
Attach to sandboxes
# at spawn
sb = qbox.Sandbox.create("python-3.12", volumes=[qbox.Mount("data", "/mnt/data")])
# or on a running sandbox
sb.volumes.attach("data", "/mnt/data")
other.volumes.attach("data", "/data", read_only=True) # same volume, different sandbox + path
sb.volumes.list() # attachments + their mount state
sb.volumes.remount("data") # retry after fixing credentials
sb.volumes.detach("data")
A volume attaches to a given sandbox once. To mount the same bucket at two paths in one sandbox, create two volumes. Across different sandboxes you can mount the same volume at whatever paths you like.
What attaching does
For each attached volume, qbox derives a set of environment variables and makes
them available in the sandbox (source="volume" in envs.list()):
QBOX_VOLUME_data_TYPE=s3
QBOX_VOLUME_data_BUCKET=my-bucket
QBOX_VOLUME_data_REGION=us-east-1
QBOX_VOLUME_data_ACCESS_KEY=AKIA...
QBOX_VOLUME_data_SECRET_KEY=...
QBOX_VOLUME_data_MOUNT_PATH=/mnt/data
QBOX_VOLUME_data_READ_ONLY=false
Single-volume convenience: if a sandbox has exactly one S3-flavored volume
(s3/r2/s3_compatible) and you haven’t set AWS_ACCESS_KEY_ID yourself, qbox
also fills AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION (and
AWS_ENDPOINT_URL for R2/S3-compatible) so boto3/aws-cli/rclone just work.
Auto-mount vs. manual
Whether the volume is actually mounted as a filesystem depends on the template’s base image:
| Image | Behaviour | Attachment state |
|---|---|---|
ships the qbox volumes service (mount-s3, gcsfuse, blobfuse2 + /etc/qbox/services/volumes.json) | qbox auto-mounts at the mount path | mounted (or failed with a reason) |
| no volumes service | qbox sets the envs + creates the mount point, but doesn’t mount | manual |
In the manual case nothing is mounted for you — use the QBOX_VOLUME_* envs
to mount it yourself (or boto3/rclone straight against the bucket). The qbox
base images (qbox-code-interpreter, …) ship the volumes service; a plain
FROM debian:bookworm template does not.
Mounts are unmounted cleanly when the sandbox terminates, flushing pending writes back to the bucket.
CLI
qbox volumes create data --type s3 \
-c bucket=my-bucket -c region=us-east-1 -c access_key_id=AKIA... -c secret_access_key=...
qbox volumes list
qbox volumes delete data --force # --force if attached anywhere
qbox sandboxes volumes attach sb_123 data --mount-path /mnt/data
qbox sandboxes volumes list sb_123
qbox sandboxes volumes detach sb_123 data
Providers & config fields
| Type | Required config |
|---|---|
s3 | bucket, region, access_key_id, secret_access_key (optional endpoint, prefix, session_token) |
r2 | bucket, account_id, access_key_id, secret_access_key (endpoint derived from account_id) |
s3_compatible | bucket, endpoint (no path), access_key_id, secret_access_key (optional region, force_path_style) |
gcs | bucket, service_account_key_json |
azure_blob | account, container, account_key |
S3-compatible endpoints work with MinIO, DigitalOcean Spaces, Backblaze B2,
Linode, Hetzner, etc. — set endpoint to the service’s S3 URL.
HTTP & scopes
Reusable volumes live at /v1/volumes (GET/POST, GET/PATCH/DELETE /{id}); attachments at /v1/sandboxes/{id}/volumes. Reading metadata needs
sandboxes:read; creating/attaching needs the sandboxes:volumes scope. The
API never returns secret credentials — only a non-secret configSummary
(bucket, region, endpoint). To rotate credentials, PATCH the volume with a
full new config; every sandbox it’s attached to is re-pushed.