Beta Feature: Persistent volumes are in beta and currently free (not billed). Pricing will be announced before general availability.
Overview
Persistent volumes store data that survives sandbox restarts. With one volume per sandbox, the recommended approach is to mount your entire workspace.
Prerequisites
- Installed the Sandbox SDK (see Quickstart)
- Created a sandbox instance
- Basic understanding of file paths
Quick start
Mount a volume at /workspace to persist your entire project:
const sandbox = await client.sandboxes.create({
image: "node:20",
volumes: {
"/workspace": "my_project"
}
});
// Write code
await sandbox.files.write("/workspace/app.js", "console.log('hello')");
// Install dependencies
sandbox.cwd("/workspace");
await sandbox.exec("npm install express");
// Everything persists after deletion
await sandbox.kill();
// Create new sandbox - all files still there
const sandbox2 = await client.sandboxes.create({
image: "node:20",
volumes: {
"/workspace": "my_project"
}
});
const result = await sandbox2.exec("ls /workspace");
console.log(result.stdout); // Shows app.js, node_modules, etc.
Current limitation: only one volume per sandbox. Mount your entire workspace at /workspace or /app to persist everything.
Creating volumes
Simple volume (1GB default)
const sandbox = await client.sandboxes.create({
image: "node:20",
volumes: {
"/workspace": "my_project"
}
});
Custom volume size
Specify size in gigabytes (1-100 GB):
const sandbox = await client.sandboxes.create({
image: "node:20",
volumes: {
"/workspace": {
name: "my_project",
sizeGb: 10
}
}
});
Volume lifecycle
- Create: Volume is created automatically on first use
- Persist: Data remains after sandbox deletion
- Reuse: Mount by name in new sandboxes
- Delete: Explicitly delete when no longer needed
Common workflows
Full workspace persistence
Mount at /workspace to persist code, dependencies, and build artifacts:
const sandbox = await client.sandboxes.create({
image: "node:20",
volumes: { "/workspace": "my_app" }
});
sandbox.cwd("/workspace");
await sandbox.exec("git clone https://github.com/user/repo.git .");
await sandbox.exec("npm install");
await sandbox.exec("npm run build");
await sandbox.kill();
// Later: everything is still there
const sandbox2 = await client.sandboxes.create({
image: "node:20",
volumes: { "/workspace": "my_app" }
});
sandbox2.cwd("/workspace");
await sandbox2.exec("npm test"); // No reinstall needed
Iterative development
// Day 1: Setup
const sandbox = await client.sandboxes.create({
image: "python:3.11",
volumes: { "/app": "ml_project" }
});
sandbox.cwd("/app");
await sandbox.exec("pip install torch numpy pandas");
await sandbox.files.write("/app/train.py", pythonCode);
await sandbox.kill();
// Day 2: Continue where you left off
const sandbox2 = await client.sandboxes.create({
image: "python:3.11",
volumes: { "/app": "ml_project" }
});
sandbox2.cwd("/app");
await sandbox2.exec("python train.py"); // All deps already installed
Database persistence
For services that only need data storage:
const sandbox = await client.sandboxes.create({
image: "postgres:16",
volumes: {
"/var/lib/postgresql/data": {
name: "postgres_data",
sizeGb: 10
}
},
env: {
POSTGRES_PASSWORD: "secret",
POSTGRES_DB: "myapp"
}
});
await sandbox.exec(
"docker-entrypoint.sh postgres >/tmp/postgres.log 2>&1",
{ background: true }
);
Volume management
Volume attachment
Only one sandbox can use a volume at a time:
const sandbox1 = await client.sandboxes.create({
image: "node:20",
volumes: { "/workspace": "shared_vol" }
});
// This fails - volume already attached
try {
const sandbox2 = await client.sandboxes.create({
image: "node:20",
volumes: { "/workspace": "shared_vol" }
});
} catch (error) {
// Error: Volume "shared_vol" is already attached
}
// Release volume by deleting sandbox
await sandbox1.kill();
// Now it works
const sandbox2 = await client.sandboxes.create({
image: "node:20",
volumes: { "/workspace": "shared_vol" }
});
Regional constraints
Volumes and sandboxes must be in the same region:
// Volume created in IAD
const sandbox1 = await client.sandboxes.create({
image: "node:20",
volumes: { "/workspace": "my_vol" }
});
// This fails - different region
try {
const sandbox2 = await client.sandboxes.create({
image: "node:20",
region: "lax",
volumes: { "/workspace": "my_vol" }
});
} catch (error) {
// Error: Volume "my_vol" is in region iad
}
Volume naming rules
- Lowercase letters, numbers, and underscores only
- Maximum 30 characters
// ✅ Valid
"my_project"
"workspace_2024"
"app_v2"
// ❌ Invalid
"My-Project" // No uppercase or hyphens
"cache@v2" // No special characters
Error handling
Common errors
// Invalid name
try {
await client.sandboxes.create({
image: "node:20",
volumes: { "/workspace": "my-project" } // Hyphen not allowed
});
} catch (error) {
// "Volume name must be lowercase alphanumeric and underscores"
}
// Size limit exceeded
try {
await client.sandboxes.create({
image: "node:20",
volumes: {
"/workspace": { name: "big_vol", sizeGb: 500 } // Max is 100
}
});
} catch (error) {
// "Size must be 1-100 GB"
}
// Multiple volumes
try {
await client.sandboxes.create({
image: "node:20",
volumes: {
"/workspace": "vol1",
"/cache": "vol2" // Only one volume allowed
}
});
} catch (error) {
// "Only one volume can be mounted per sandbox"
}
Best practices
Mount at /workspace or /app: With one volume per sandbox, mount your entire project to persist code, dependencies, and build artifacts together.
Use descriptive names: Name volumes by project or purpose (ml_project, api_backend) rather than generic names (volume1, data).
Right-size volumes: Start with 1-5 GB and increase if needed. Volumes cannot be resized after creation.
One volume per sandbox: You can only mount one volume per sandbox. Choose your mount point carefully.
Single attachment: A volume can only be attached to one sandbox at a time. Delete the sandbox to release the volume.
Limitations
- One volume per sandbox: Current limitation
- Size range: 1-100 GB per volume
- Regional binding: Volumes cannot move between regions
- Single attachment: One volume attached to one sandbox at a time
- No resizing: Volume size is fixed at creation
- Beta feature: Not currently billed (pricing TBA before GA)
Pricing
Persistent volumes are free during beta. Pricing will be announced before general availability.
Next steps