Skip to main content
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.
npm 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 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):
import { Sandbox, Volume } from 'e2b'

const [sourceName, destName] = process.argv.slice(2)
if (!sourceName || !destName) {
  throw new Error('Usage: copy-volume <source-volume> <dest-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()
}

Usage

Run the script with the source and destination volume names. For example, to copy everything from prod-data into prod-data-backup:
export E2B_API_KEY=your-api-key
npx tsx copy-volume.mts 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.