diff --git a/docs.json b/docs.json
index 60e17679..0d81fff0 100644
--- a/docs.json
+++ b/docs.json
@@ -197,7 +197,8 @@
"docs/volumes/read-write",
"docs/volumes/info",
"docs/volumes/upload",
- "docs/volumes/download"
+ "docs/volumes/download",
+ "docs/volumes/migrate"
]
},
{
diff --git a/docs/volumes.mdx b/docs/volumes.mdx
index 8f98d378..35e8ba49 100644
--- a/docs/volumes.mdx
+++ b/docs/volumes.mdx
@@ -1,5 +1,6 @@
---
title: "Volumes"
+description: "Persistent storage that exists independently of sandboxes and can be mounted across multiple sandboxes."
sidebarTitle: Overview
---
diff --git a/docs/volumes/download.mdx b/docs/volumes/download.mdx
index e2f0ef2a..aea31cc6 100644
--- a/docs/volumes/download.mdx
+++ b/docs/volumes/download.mdx
@@ -1,5 +1,6 @@
---
title: "Download data from volume"
+description: "Download files from a volume to your local filesystem with the E2B SDK."
sidebarTitle: Download data
---
diff --git a/docs/volumes/info.mdx b/docs/volumes/info.mdx
index 91c3c7ef..2700a43e 100644
--- a/docs/volumes/info.mdx
+++ b/docs/volumes/info.mdx
@@ -1,5 +1,6 @@
---
title: "Get information about a file or directory"
+description: "Get metadata, check existence, and update permissions for files and directories in a volume."
sidebarTitle: File & directory metadata
---
diff --git a/docs/volumes/manage.mdx b/docs/volumes/manage.mdx
index 08bbd6c0..6794f506 100644
--- a/docs/volumes/manage.mdx
+++ b/docs/volumes/manage.mdx
@@ -1,5 +1,6 @@
---
title: "Managing volumes"
+description: "Create, connect to, list, inspect, and destroy volumes with the E2B SDK."
---
## Create a volume
diff --git a/docs/volumes/migrate.mdx b/docs/volumes/migrate.mdx
new file mode 100644
index 00000000..be401367
--- /dev/null
+++ b/docs/volumes/migrate.mdx
@@ -0,0 +1,168 @@
+---
+title: "Migrating data between volumes"
+description: "Copy all data from one E2B volume to another by mounting both into a sandbox and using rsync."
+---
+
+When you need to move data from an old volume to a new one, the simplest approach is to mount both volumes into a single sandbox and copy the files across. This page walks through a script that does exactly that: it spins up a sandbox from the **base** template, mounts a source and a destination volume, and copies all data from source to destination.
+
+- The **source** volume must already exist. If it doesn't, the script fails.
+- The **destination** volume is reused if it already exists, or created if it doesn't.
+
+## Prerequisites
+
+You need an E2B account with your API key set as `E2B_API_KEY` in your environment, and the E2B SDK installed.
+
+
+```bash JavaScript & TypeScript
+npm install e2b
+```
+```bash Python
+pip install e2b
+```
+
+
+## The script
+
+The script first resolves both volumes by name, connecting to the source and connecting to (or creating) the destination. It then mounts both into a sandbox and uses [rsync](https://rsync.samba.org/) to copy the data across. Reading the volume type from the token is optional, but it's a handy sanity check that you're copying between the volumes you expect.
+
+Save this as `copy-volume.mts` (JavaScript & TypeScript) or `copy_volume.py` (Python):
+
+
+```ts JavaScript & TypeScript
+import { Sandbox, Volume } from 'e2b'
+
+const [sourceName, destName] = process.argv.slice(2)
+if (!sourceName || !destName) {
+ throw new Error('Usage: copy-volume ')
+}
+
+async function findVolumeByName(name: string) {
+ const volumes = await Volume.list()
+ return volumes.find((v) => v.name === name) ?? null
+}
+
+const sourceInfo = await findVolumeByName(sourceName)
+if (!sourceInfo) {
+ throw new Error(`Source volume "${sourceName}" does not exist`)
+}
+const sourceVolume = await Volume.connect(sourceInfo.volumeId)
+
+const destInfo = await findVolumeByName(destName)
+const destVolume = destInfo
+ ? await Volume.connect(destInfo.volumeId)
+ : await Volume.create(destName)
+
+function voltype(token: string): string {
+ // JWT payloads are base64url and often omit padding, so normalize before decoding
+ let segment = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')
+ segment += '='.repeat((4 - (segment.length % 4)) % 4)
+ const payload = JSON.parse(atob(segment))
+ return payload.voltype
+}
+
+console.log(`Source volume type: ${voltype(sourceVolume.token)}`)
+console.log(`Destination volume type: ${voltype(destVolume.token)}`)
+
+const sandbox = await Sandbox.create({
+ volumeMounts: {
+ '/mnt/source': sourceVolume,
+ '/mnt/dest': destVolume,
+ },
+})
+
+try {
+ const install = await sandbox.commands.run(
+ 'sudo apt-get update && sudo apt-get install -y rsync',
+ )
+ if (install.exitCode !== 0) {
+ throw new Error(`rsync install failed: ${install.stderr}`)
+ }
+
+ const result = await sandbox.commands.run('rsync -a /mnt/source/ /mnt/dest/')
+ if (result.exitCode !== 0) {
+ throw new Error(`Copy failed: ${result.stderr}`)
+ }
+
+ const listing = await sandbox.commands.run('ls -la /mnt/dest')
+ console.log(listing.stdout)
+} finally {
+ await sandbox.kill()
+}
+```
+```python Python
+import base64
+import json
+import sys
+
+from e2b import Sandbox, Volume
+
+args = sys.argv[1:]
+if len(args) != 2:
+ raise SystemExit('Usage: copy_volume ')
+source_name, dest_name = args
+
+
+def find_volume_by_name(name):
+ return next((v for v in Volume.list() if v.name == name), None)
+
+
+def voltype(token):
+ payload = token.split('.')[1]
+ payload += '=' * (-len(payload) % 4) # JWT segments omit base64 padding
+ return json.loads(base64.urlsafe_b64decode(payload))['voltype']
+
+
+source_info = find_volume_by_name(source_name)
+if source_info is None:
+ raise SystemExit(f'Source volume "{source_name}" does not exist')
+source_volume = Volume.connect(source_info.volume_id)
+
+dest_info = find_volume_by_name(dest_name)
+dest_volume = (
+ Volume.connect(dest_info.volume_id) if dest_info else Volume.create(dest_name)
+)
+
+print(f'Source volume type: {voltype(source_volume.token)}')
+print(f'Destination volume type: {voltype(dest_volume.token)}')
+
+sandbox = Sandbox.create(
+ volume_mounts={
+ '/mnt/source': source_volume,
+ '/mnt/dest': dest_volume,
+ },
+)
+
+try:
+ install = sandbox.commands.run(
+ 'sudo apt-get update && sudo apt-get install -y rsync',
+ )
+ if install.exit_code != 0:
+ raise RuntimeError(f'rsync install failed: {install.stderr}')
+
+ result = sandbox.commands.run('rsync -a /mnt/source/ /mnt/dest/')
+ if result.exit_code != 0:
+ raise RuntimeError(f'Copy failed: {result.stderr}')
+
+ listing = sandbox.commands.run('ls -la /mnt/dest')
+ print(listing.stdout)
+finally:
+ sandbox.kill()
+```
+
+
+## Usage
+
+Run the script with the source and destination volume names. For example, to copy everything from `prod-data` into `prod-data-backup`:
+
+
+```bash JavaScript & TypeScript
+export E2B_API_KEY=your-api-key
+npx tsx copy-volume.mts prod-data prod-data-backup
+```
+```bash Python
+export E2B_API_KEY=your-api-key
+python copy_volume.py prod-data prod-data-backup
+```
+
+
+On success it prints a listing of the destination so you can confirm the data landed. The sandbox is always shut down afterward, but both volumes (and their data) persist.
diff --git a/docs/volumes/mount.mdx b/docs/volumes/mount.mdx
index ab02a12d..8f199882 100644
--- a/docs/volumes/mount.mdx
+++ b/docs/volumes/mount.mdx
@@ -1,5 +1,6 @@
---
title: "Mounting volumes"
+description: "Mount one or more volumes to a sandbox at custom paths when creating it."
---
You can mount one or more volumes to a sandbox when creating it. The keys of the `volumeMounts` / `volume_mounts` object are the mount paths inside the sandbox.
diff --git a/docs/volumes/read-write.mdx b/docs/volumes/read-write.mdx
index 43838b6c..cd644e7e 100644
--- a/docs/volumes/read-write.mdx
+++ b/docs/volumes/read-write.mdx
@@ -1,5 +1,6 @@
---
title: "Read & write files"
+description: "Read, write, list, and remove files and directories in a volume with the SDK."
sidebarTitle: Read & write
---
diff --git a/docs/volumes/upload.mdx b/docs/volumes/upload.mdx
index dee64c68..6ac64e48 100644
--- a/docs/volumes/upload.mdx
+++ b/docs/volumes/upload.mdx
@@ -1,5 +1,6 @@
---
title: "Upload data to volume"
+description: "Upload single files or entire directories from your local filesystem to a volume."
sidebarTitle: Upload data
---