DocsPause, resume and snapshots

Pause, resume and snapshots

Pause a sandbox to stop paying for it and resume it later where it stopped, or take a snapshot to start new sandboxes from. A paused sandbox keeps its memory and its disk, and costs nothing while it is paused.

Pause a sandbox

pause() stops the sandbox and writes its state to a snapshot. Memory is kept by default, so running processes and open files survive.

import { Sandbox } from '@impello/sdk'

// Reads IMPELLO_API_KEY from the environment.
const sandbox = await Sandbox.create('base')
await sandbox.files.write('/home/user/state.json', '{"step": 1}')

const paused = await sandbox.pause()
console.log(paused) // true

The call returns true when it paused the sandbox and false when the sandbox was already paused, so pausing twice is safe. A sandbox that does not exist raises SandboxNotFoundError (SandboxNotFoundException in Python); see Errors.

Both SDKs also expose pause as a static call, so you can pause by ID without holding an instance: Sandbox.pause(sandboxId) in TypeScript and Sandbox.pause(sandbox_id) in Python.

Resume

Sandbox.connect(sandboxId) resumes a paused sandbox and hands back a client for it. The sandbox must be running or paused; anything else raises SandboxNotFoundError.

import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')
const sandboxId = sandbox.sandboxId
await sandbox.pause()

// Later, in another process.
const resumed = await Sandbox.connect(sandboxId, { timeoutMs: 600_000 })
console.log(await resumed.files.read('/home/user/state.json'))

await resumed.kill()

A resumed sandbox starts a fresh timeout: 5 minutes unless you pass one. timeoutMs is milliseconds in TypeScript, timeout is seconds in Python. For a running sandbox the timeout only moves if the new value is longer than the one already set. See Timeouts.

An instance you already hold has the same method: await sandbox.connect() in TypeScript, sandbox.connect() in Python. That is the shortest way back into a sandbox you paused a moment ago.

Pause without memory

Pass keepMemory: false (keep_memory=False in Python) to drop the in-memory state and persist only the filesystem. Resuming such a sandbox cold-boots it from disk: the files are there, the running processes and open connections are not. A sandbox paused this way must be resumed explicitly with connect().

import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')
await sandbox.pause({ keepMemory: false })

pause() takes one option.

TypeScriptPythonDefaultWhat it does
keepMemorykeep_memorytrue / TrueKeep a full memory snapshot. false persists the filesystem only

Take a snapshot

A snapshot is a persistent copy of a sandbox that you can start new sandboxes from. createSnapshot() pauses the sandbox while it takes the copy, and the sandbox is running again when the call returns. The call returns the snapshot's ID together with its names.

import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')
await sandbox.files.write('/home/user/state.json', '{"step": 1}')

const snapshot = await sandbox.createSnapshot({ name: 'my-snapshot' })
console.log(snapshot.snapshotId, snapshot.names)

// Start a fresh sandbox from it.
const fromSnapshot = await Sandbox.create(snapshot.snapshotId)
console.log(await fromSnapshot.files.read('/home/user/state.json'))

await fromSnapshot.kill()
// Kill the source sandbox if you are done with it.
await sandbox.kill()

name is optional. Passing one that already exists adds a new build to that snapshot instead of creating a second one, so a nightly job can keep writing to the same name. snapshotId (snapshot_id) is the namespaced name with its tag, for example your-team/my-snapshot:default. names holds the namespaced names without the tag, for example your-team/my-snapshot, and is empty for a snapshot you did not name. The part before the slash is your team namespace, not a project name.

Snapshots are persistent and survive the sandbox they came from: killing the source sandbox does not remove them, and they only go when you delete them.

Snapshots are not billed; see Limits and pricing. A deleted snapshot's data is removed within 30 days.

List snapshots

listSnapshots() returns a paginator. Called on an instance it lists only that sandbox's snapshots; called on Sandbox it lists all of them.

import { Sandbox } from '@impello/sdk'

const paginator = Sandbox.listSnapshots({ limit: 50 })

while (paginator.hasNext) {
  for (const snapshot of await paginator.nextItems()) {
    console.log(snapshot.snapshotId, snapshot.names)
  }
}

The static form takes sandboxId (sandbox_id); both forms take limit and nextToken (next_token). The Python SDK also takes name, which filters by snapshot name or ID and accepts a tag — "my-snapshot", "your-team/my-snapshot" or "my-snapshot:v1". The TypeScript SDK has no name filter.

Delete a snapshot

Pass the snapshotId (snapshot_id) that createSnapshot handed you. It is a namespaced name with its tag, or the template ID with its tag (for example abc123:default) when you did not pass a name.

import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')
const snapshot = await sandbox.createSnapshot({ name: 'my-snapshot' })

const deleted = await Sandbox.deleteSnapshot(snapshot.snapshotId)
console.log(deleted) // false if there was no such snapshot

Fork a running sandbox

fork() checkpoints the sandbox in place: it is briefly paused, snapshotted with its full memory state, and resumed, keeping its ID and its expiry. It then starts count new sandboxes from that snapshot. The snapshot is captured once however many forks you ask for. In TypeScript, fork() needs @impello/sdk 0.3.2 or later.

import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')
await sandbox.commands.run('echo warm > /home/user/cache')

const forks = await sandbox.fork({ count: 2, timeoutMs: 600_000 })

for (const fork of forks) {
  if (fork instanceof Error) {
    throw fork
  }
  const result = await fork.commands.run('cat /home/user/cache')
  console.log(fork.sandboxId, result.stdout)
}

count defaults to 1. The forked sandboxes get their own timeout — timeoutMs in TypeScript, in milliseconds, and timeout in Python, in seconds — defaulting to 5 minutes. A count below 1 raises InvalidArgumentError (InvalidArgumentException in Python).

There is a static form for forking by ID: Sandbox.fork(sandboxId, { count: 2 }) in TypeScript, Sandbox.fork(sandbox_id, count=2) in Python.

How long a paused sandbox lives

A sandbox that has sat paused for 14 days is deleted along with its disk. The clock restarts every time you pause, so a sandbox you resume and pause again gets a fresh 14 days. Snapshots are not affected by this; they persist until you delete them.

Paused sandboxes are not billed. They hold their disk and cost nothing, and billing starts again when they resume.

A paused sandbox does not count against your concurrency limit, and there is no limit on how many sandboxes you keep paused. See Limits and pricing.

To see what you have paused, filter the sandbox list by state.

import { Sandbox } from '@impello/sdk'

const paginator = Sandbox.list({ query: { state: ['paused'] } })

while (paginator.hasNext) {
  for (const s of await paginator.nextItems()) {
    console.log(s.sandboxId, s.state)
  }
}

AsyncSandbox has the same methods with every network call awaited, including fork() — except list() and list_snapshots(), which stay synchronous and hand back an AsyncSandboxPaginator / AsyncSnapshotPaginator whose next_items() you await.

Next: Metrics.