---
title: Snapshots
description: Save files or running workloads and restore them into new sandboxes
icon: "code-branch"
---

<Tooltip tip="Cloud supports disk snapshots from stopped or crashed sandboxes and disk restore. Full snapshots, forking, groups, and local archives are not yet available on cloud."><span className="msb-badge-limited">Limited on cloud <Icon icon="circle-info" size={11} /></span></Tooltip>

Save a prepared sandbox and reuse it as a starting point. Each restore creates a new sandbox and leaves the source unchanged.

## Snapshot types

Disk is the default. Full adds memory and execution state; there is no memory-only snapshot.

| Type | Saves | On restore |
| --- | --- | --- |
| **Disk** | Files and owned volumes | Boots a new VM |
| **Full** | Disk, memory, and running processes | Resumes execution |

## Disk snapshots

Use disk snapshots for installed dependencies, prepared workspaces, and saved files. Cloud requires a stopped or crashed source. See [guest flushing](#guest-flushing) for how pending writes are handled.

<Steps>
<Step title="Prepare">

Start a `python:3.12` sandbox named `baseline` using the [create guide](/sandboxes/lifecycle#create-a-sandbox), then install a dependency:

<CodeGroup>
```typescript TypeScript
const baseline = await Sandbox.get("baseline");

const session = await baseline.connect();
await session.exec("pip", ["install", "requests"]);

await baseline.stop();
```

```rust Rust
let baseline = Sandbox::get("baseline").await?;

let session = baseline.connect().await?;
session.exec("pip", ["install", "requests"]).await?;

baseline.stop().await?;
```

```python Python
baseline = await Sandbox.get("baseline")

session = await baseline.connect()
await session.exec("pip", ["install", "requests"])

await baseline.stop()
```

```go Go
baseline, err := m.GetSandbox(ctx, "baseline")
if err != nil { return err }

session, err := baseline.Connect(ctx)
if err != nil { return err }

_, err = session.Exec(ctx, "pip", []string{"install", "requests"})
if err != nil { return err }

err = baseline.Stop(ctx)
if err != nil { return err }
```

```bash CLI
msb exec baseline -- pip install requests
msb stop baseline
```
</CodeGroup>

</Step>
<Step title="Capture">

Save the prepared state as `ready`:

<CodeGroup>
```typescript TypeScript
import { Sandbox } from "microsandbox";

const baseline = await Sandbox.get("baseline");

const snapshot = await baseline.snapshot("ready");
```

```rust Rust
use microsandbox::Sandbox;

let baseline = Sandbox::get("baseline").await?;

let snapshot = baseline.snapshot("ready").await?;
```

```python Python
from microsandbox import Sandbox

baseline = await Sandbox.get("baseline")

snapshot = await baseline.snapshot("ready")
```

```go Go
baseline, err := m.GetSandbox(ctx, "baseline")
if err != nil {
    return err
}

snapshot, err := baseline.Snapshot(ctx, "ready")
if err != nil {
    return err
}
```

```bash CLI
msb snap create ready --sandbox baseline
```
</CodeGroup>

</Step>
<Step title="Restore">

Restore into `worker`. Its disk writes are independent of the source.

<CodeGroup>
```typescript TypeScript
const worker = await Sandbox.restore(snapshot)
    .name("worker")
    .restore();
```

```rust Rust
let worker = Sandbox::restore_ref(snapshot.reference())
    .name("worker")
    .restore()
    .await?;
```

```python Python
worker = await Sandbox.restore(snapshot, name="worker")
```

```go Go
worker, err := m.RestoreSandbox(ctx, snapshot, "worker")
if err != nil {
    return err
}
```

```bash CLI
# Local snapshot selector
msb snap restore baseline:ready --name worker
msb exec worker -- python -c "import requests; print(requests.__version__)"
```
</CodeGroup>

</Step>
</Steps>

Local snapshots use `group:member` selectors, such as `baseline:ready`. For cloud, pass the snapshot object or reference; it has no client-local path.

## Full snapshots

<Tooltip tip="Full snapshots are not yet available on microsandbox cloud."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

Capture a running workload to resume its processes and memory later. A running source briefly pauses during capture, then resumes; an already-paused source stays paused.

On Windows, full snapshots now preserve all tracked hardlink names for bind mounts. Runtimes that support this format can still restore the previous Windows filesystem format. Older runtimes cannot restore snapshots written with the new bind-mount format; use the updated runtime when restoring them. This does not change disk-only snapshot compatibility.

<Steps>
<Step title="Capture">

Start with a running or paused sandbox named `worker`:

<CodeGroup>
```typescript TypeScript
import { Snapshot } from "microsandbox";

const snapshot = await Snapshot.builder("snap")
    .fromSandbox("worker")
    .full()
    .create();
```

```rust Rust
use microsandbox::Snapshot;

let snapshot = Snapshot::builder("snap")
    .from_sandbox("worker")
    .full()
    .create()
    .await?;
```

```python Python
from microsandbox import Snapshot

snapshot = await Snapshot.create(
    "snap",
    from_sandbox="worker",
    full=True,
)
```

```go Go
snapshot, err := m.Snapshot.Create(ctx, m.SnapshotCreateOptions{
    Name:        "snap",
    FromSandbox: "worker",
    Full:        true,
})
```

```bash CLI
msb snap create snap --sandbox worker --full
```
</CodeGroup>

</Step>
<Step title="Restore">

Resume in a new sandbox. The image’s startup command does not run again.

<CodeGroup>
```typescript TypeScript
const resumed = await Sandbox.restore("worker:snap")
    .name("resumed")
    .restore();
```

```rust Rust
let resumed = Sandbox::restore("worker:snap")
    .name("resumed")
    .restore()
    .await?;
```

```python Python
resumed = await Sandbox.restore("worker:snap", name="resumed")
```

```go Go
resumed, err := m.RestoreSandbox(ctx, "worker:snap", "resumed")
if err != nil { return err }
```

```bash CLI
msb snap restore worker:snap --name resumed
```
</CodeGroup>

</Step>
</Steps>

- **Compatibility:** Use a compatible runtime and guest kernel. CPU and memory settings, including maximums, must match the capture.
- **Connections:** Reconnect clients and application connections. Host-side connections and transfers are not captured; buffered process output is not replayed.
- **Mounts:** Owned volumes are private to each child. Host bindings need explicit authorization; see [mount behavior](#mount-behavior).

<Accordion title="Capture or restore fails">

If capture saves an artifact but cannot recover the source, inspect the artifact reported in the error before retrying. A packaging failure may leave only a runtime checkpoint.

If restore leaves an incomplete child, remove it and restore again; the snapshot remains reusable. Older development full snapshots may need recapture or disk-only restore.

</Accordion>

### Share memory

Full restores normally copy memory. Enable copy-on-write (CoW) to share unchanged pages between children while keeping writes private:

<CodeGroup>
```typescript TypeScript
const worker_a = await Sandbox.restore("worker:snap")
    .name("worker_a")
    .cowMemory()
    .restore();

const worker_b = await Sandbox.restore("worker:snap")
    .name("worker_b")
    .cowMemory()
    .restore();
```

```rust Rust
let worker_a = Sandbox::restore("worker:snap")
    .name("worker_a")
    .cow_memory()
    .restore()
    .await?;

let worker_b = Sandbox::restore("worker:snap")
    .name("worker_b")
    .cow_memory()
    .restore()
    .await?;
```

```python Python
worker_a = await Sandbox.restore(
    "worker:snap",
    name="worker_a",
    cow_memory=True,
)

worker_b = await Sandbox.restore(
    "worker:snap",
    name="worker_b",
    cow_memory=True,
)
```

```go Go
worker_a, err := m.RestoreSandbox(ctx, "worker:snap", "worker_a",
    m.WithCowMemory(),
)
if err != nil { return err }

worker_b, err := m.RestoreSandbox(ctx, "worker:snap", "worker_b",
    m.WithCowMemory(),
)
if err != nil { return err }
```

```bash CLI
msb snap restore worker:snap --name worker_a --cow-mem
msb snap restore worker:snap --name worker_b --cow-mem
```
</CodeGroup>

The first restore prepares a local memory cache; later restores reuse it. No special source setup is needed. CoW requires a full snapshot and cannot be combined with disk-only restore.

### Restore disk only

Boot a fresh VM from a full snapshot’s saved disk without resuming its processes. You can choose different CPU and memory settings.

<CodeGroup>
```typescript TypeScript
const rebooted = await Sandbox.restore("worker:snap")
    .name("rebooted")
    .diskOnly()
    .restore();
```

```rust Rust
let rebooted = Sandbox::restore("worker:snap")
    .name("rebooted")
    .disk_only()
    .restore()
    .await?;
```

```python Python
rebooted = await Sandbox.restore(
    "worker:snap",
    name="rebooted",
    disk_only=True,
)
```

```go Go
rebooted, err := m.RestoreSandbox(ctx, "worker:snap", "rebooted",
    m.WithSnapshotDiskOnly(),
)
if err != nil { return err }
```

```bash CLI
msb snap restore worker:snap --name rebooted --disk-only
```
</CodeGroup>

Snapshots with a tmpfs root cannot be restored disk-only.

### Keep the guest clock

By default (`sync`), the sandbox's date and time follow the host. The host updates the clock at boot, about once a minute, and when a full snapshot restores or a paused sandbox resumes.

With `off`, the host stops updating the clock after boot. Full restores continue from the snapshot's saved time. The clock keeps running, making this useful for tests that need a repeatable starting time.

<CodeGroup>
```rust Rust
use microsandbox::sandbox::GuestClockPolicy;

let source = Sandbox::builder("worker")
    .image("alpine")
    .guest_clock(GuestClockPolicy::Off)
    .create()
    .await?;
```

```bash CLI
msb create alpine --name worker --guest-clock off
```
</CodeGroup>

Full snapshots save this setting. Restores inherit it unless you choose `sync` to use host time or `off` to continue from the saved time:

<CodeGroup>
```rust Rust
let child = Sandbox::restore("worker:snap")
    .name("child")
    .guest_clock(GuestClockPolicy::Sync)
    .restore()
    .await?;
```

```bash CLI
msb snap restore worker:snap --name child --guest-clock sync
```
</CodeGroup>

- **Scope:** `off` prevents host clock updates. Programs inside the sandbox with permission to set the time can still change it.
- **Drift:** With `off`, the guest clock is not corrected after the host sleeps or its clock changes.
- **Monotonic clock:** Continues from the snapshot in both modes.
- **Forks:** Forks inherit this setting.
- **Compatibility:** Older runtimes reject `off`. Older SDKs and CLIs reject full snapshots saved with `off`, including exported archives. Cloud also rejects `off`.
- **Downgrades:** Older SDKs and CLIs require a database downgrade to reopen this installation. Downgrading is blocked while any sandbox's saved or active settings use `off`.

<a id="guest-filesystem-flush"></a>

### Guest flushing

Guest flushing writes pending filesystem changes from memory to disk. It works the same way in the CLI and all SDKs, for snapshots, forking, and pausing.

The default `auto` policy flushes running disk snapshots. Full snapshots and forks keep those changes in memory instead. Choose `required` if you plan to restore a full snapshot's disks without its memory:

<CodeGroup>
```typescript TypeScript
import { Snapshot } from "microsandbox";

const snapshot = await Snapshot.builder("ready")
    .fromSandbox("worker")
    .full()
    .guestFlush("required")
    .create();
```

```rust Rust
use microsandbox::{snapshot::GuestFlush, Snapshot};

let snapshot = Snapshot::builder("ready")
    .from_sandbox("worker")
    .full()
    .guest_flush(GuestFlush::Required)
    .create()
    .await?;
```

```python Python
from microsandbox import GuestFlush, Snapshot

snapshot = await Snapshot.create(
    "ready",
    from_sandbox="worker",
    full=True,
    guest_flush=GuestFlush.REQUIRED,
)
```

```go Go
import m "github.com/superradcompany/microsandbox/sdk/go"

snapshot, err := m.Snapshot.Create(ctx, m.SnapshotCreateOptions{
    Name:        "ready",
    FromSandbox: "worker",
    Full:        true,
    GuestFlush:  m.GuestFlushRequired,
})
if err != nil { return err }
```

```bash CLI
msb snap create ready --sandbox worker --full --guest-flush required
```
</CodeGroup>

Flushing does not save data still buffered by your application or commit database transactions. Save or commit that data before capturing the snapshot.

<Accordion title="Advanced flush behavior">

| Policy | Behavior |
| --- | --- |
| `auto` (default) | Flush before a running disk-only capture; no extra flush for full captures, forking, or pausing |
| `required` | Require a successful flush before capture or pause |
| `skip` | Skip guest flushing; disk-only snapshots may miss buffered writes and need filesystem recovery |

- **Paused sandboxes:** Pause with `required` before taking a disk snapshot. Capture fails if the needed flush was not done before pausing; it never resumes the sandbox to flush.
- **Stopped or crashed sandboxes:** `auto` and `skip` capture the saved disk state. `required` fails because no guest is running.
- **Storage:** Flushing covers the root and captured block disks, including owned disks. It does not make tmpfs persistent. Host storage synchronization and snapshot durability remain enforced with every policy.
- **Compatibility:** Older runtimes may reject the policy; restart the sandbox with an updated runtime. Cloud supports only `auto`.

</Accordion>

## Snapshot history and branching

A **generation** describes how snapshot history progresses. A **branch** describes a distinct path through that history. Taking successive snapshots advances a lineage; restoring an earlier snapshot and capturing new state creates a divergent path. A snapshot group is a namespace that can contain multiple paths, with a selected head. Branches here describe ancestry, not a separate live sandbox operation or a named branch-management API.

**Forking** duplicates live execution. **Restoring** starts a sandbox from a saved snapshot. For copy-on-write memory during full restore, use `--cow-mem` in the CLI, `.cowMemory()` in TypeScript, `.cow_memory()` in Rust, `cow_memory=True` in Python, or `WithCowMemory()` in Go. The former `--forked` flag and corresponding SDK restore options remain deprecated aliases with the same behavior. CoW restore memory is off by default; live forks always use it. The MCP `sandbox_restore` tool retains its `forked` input for compatibility; it selects copy-on-write restore memory, not live forking.

### Migrating restore options

Existing callers can migrate gradually. Keep using the old name until you update the caller; both spellings enable the same memory policy and retain the same restore validation.

| Deprecated name | Preferred name | Deprecation notice |
| --- | --- | --- |
| CLI `--forked` | `--cow-mem` | Warning on stderr, including with `--quiet` |
| TypeScript `.forked()` | `.cowMemory()` | `@deprecated` annotation and a Node `DeprecationWarning` once per process through the SDK |
| Rust `.forked()` | `.cow_memory()` | Compiler deprecation warning |
| Python `forked=True` | `cow_memory=True` | `DeprecationWarning`, subject to Python's warning filters |
| Go `WithForked()` / `RestoreConfig.Forked` | `WithCowMemory()` / `RestoreConfig.CowMemory` | `Deprecated:` annotations for documentation and tooling |

<span id="branching" />

## Forking

<Tooltip tip="Forking is not yet available on microsandbox cloud."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

`fork` is the live operation; the former `branch` SDK methods remain deprecated aliases.

Create a child directly from a running or paused sandbox, without saving a reusable snapshot. Like full restore, forking preserves disk and execution state. Memory sharing is built in; each child’s writes stay private.

<CodeGroup>
```typescript TypeScript
const source = await Sandbox.get("worker");

const experiment = await source.fork("experiment");
```

```rust Rust
let source = Sandbox::get("worker").await?;

let experiment = source.fork("experiment").fork().await?;
```

```python Python
source = await Sandbox.get("worker")

experiment = await source.fork("experiment")
```

```go Go
source, err := m.GetSandbox(ctx, "worker")
if err != nil { return err }

experiment, err := source.Fork(ctx, "experiment")
if err != nil { return err }
```

```bash CLI
msb fork worker --name experiment
```
</CodeGroup>

The source returns to its previous running or paused state. Each child needs a new name and cannot inherit published host ports. Host mounts follow the same [binding rules](#external-mounts).

<Accordion title="Create several children">

Capture once for several children:

<CodeGroup>
```typescript TypeScript
const source = await Sandbox.get("worker");

const outcomes = await source.forkMany(["alice", "bob"]);
for (const outcome of outcomes) {
  if (outcome.error !== undefined) {
    console.error(`${outcome.name}: ${outcome.error.message}`);
  } else {
    console.log(`${outcome.name} started`);
    // Use the child, then stop it when finished.
    await outcome.sandbox.stop();
  }
}
```

```rust Rust
let source = Sandbox::get("worker").await?;

let outcomes = source.fork_many(["alice", "bob"]).fork().await?;
for outcome in outcomes {
    match outcome.result {
        Ok(child) => {
            println!("{} started", outcome.name);
            // Use the child, then stop it when finished.
            child.stop().await?;
        }
        Err(error) => eprintln!("{}: {error}", outcome.name),
    }
}
```

```python Python
source = await Sandbox.get("worker")

outcomes = await source.fork_many(["alice", "bob"])
for outcome in outcomes:
    if outcome.error is not None:
        print(f"{outcome.name}: {outcome.error}")
    else:
        print(f"{outcome.name} started")
        # Use the child, then stop it when finished.
        await outcome.sandbox.stop()
```

```go Go
source, err := m.GetSandbox(ctx, "worker")
if err != nil { return err }

outcomes, err := source.ForkMany(ctx, []string{"alice", "bob"})
if err != nil { return err }
for _, outcome := range outcomes {
    if outcome.Error != nil {
        fmt.Printf("%s: %v\n", outcome.Name, outcome.Error)
        continue
    }
    fmt.Printf("%s started\n", outcome.Name)
    // Use the child, then stop it and close its handle when finished.
    stopErr := outcome.Sandbox.Stop(ctx)
    outcome.Sandbox.Close()
    if stopErr != nil { return stopErr }
}
```

```bash CLI
msb fork worker --names alice bob
```
</CodeGroup>

Each child gets its own result; a failed child does not remove successful siblings. Validation or capture errors fail the call. Deleting the source or a sibling does not invalidate other children.

</Accordion>

## Mount behavior

| State | Disk snapshot | Full snapshot |
| --- | --- | --- |
| Owned volume contents | Private copy per child | Private copy per child |
| Host bind-mount files | External; not copied | External; not copied |
| Open handles and caches | Processes start fresh | Supported state resumes |

Owned directory snapshots cannot move between Unix and Windows. Owned disks do not have this restriction.

### External mounts

Host-mounted files are not included in snapshots. Supply their host paths when restoring; owned volumes restore automatically. Full restore requires all captured external filesystems and additional disks to be available.

For either a disk or full snapshot, mount a host directory like this:

<CodeGroup>
```typescript TypeScript
const child = await Sandbox.restore("worker:snap")
    .name("child")
    .volume("/work", v => v.bind("/host/project"))
    .restore();
```

```rust Rust
let child = Sandbox::restore("worker:snap")
    .name("child")
    .volume("/work", |v| v.bind("/host/project"))
    .restore()
    .await?;
```

```python Python
from microsandbox import Sandbox, Volume

child = await Sandbox.restore(
    "worker:snap",
    name="child",
    volumes={"/work": Volume.bind("/host/project")},
)
```

```go Go
child, err := m.RestoreSandbox(ctx, "worker:snap", "child",
    m.WithRestoreConfig(m.RestoreConfig{
        Volumes: map[string]m.MountConfig{
            "/work": m.Mount.Bind("/host/project", m.MountOptions{}),
        },
    }))
if err != nil { return err }
```

```bash CLI
msb snap restore worker:snap --name child -v /host/project:/work
```
</CodeGroup>

Using the same host directory for the source and restored sandbox shares its files.

<Accordion title="Advanced restore options">

- **Reuse source bindings:** Restore on the same host using the source's validated mounts. Missing dependencies still cause restore to fail.
- **Select a captured disk:** Restore a private copy of a named disk volume. External disk images are not converted to owned volumes.
- **Allow missing resources:** Continue without an external filesystem or additional disk. Root and owned storage must still be complete. Access to missing resources fails and may disrupt applications or make filesystems read-only.

Each example below restores a separate sandbox using one of these options:

<CodeGroup>
```typescript TypeScript
// Reuse source bindings.
const inherited = await Sandbox.restore("worker:snap")
    .name("inherited")
    .dangerouslyInheritResources()
    .restore();

// Select a captured disk.
const privateDisk = await Sandbox.restore("worker:snap")
    .name("private-disk")
    .volume("/data", v => v.captured())
    .restore();

// Allow missing resources.
const partial = await Sandbox.restore("worker:snap")
    .name("partial")
    .allowMissingResources()
    .restore();
```

```rust Rust
// Reuse source bindings.
let inherited = Sandbox::restore("worker:snap")
    .name("inherited")
    .dangerously_inherit_resources()
    .restore()
    .await?;

// Select a captured disk.
let private_disk = Sandbox::restore("worker:snap")
    .name("private-disk")
    .volume("/data", |v| v.captured())
    .restore()
    .await?;

// Allow missing resources.
let partial = Sandbox::restore("worker:snap")
    .name("partial")
    .allow_missing_resources()
    .restore()
    .await?;
```

```python Python
# Reuse source bindings.
inherited = await Sandbox.restore(
    "worker:snap", name="inherited", dangerously_inherit_resources=True,
)

# Select a captured disk.
private_disk = await Sandbox.restore(
    "worker:snap", name="private-disk", captured_volumes=["/data"],
)

# Allow missing resources.
partial = await Sandbox.restore(
    "worker:snap", name="partial", allow_missing_resources=True,
)
```

```go Go
// Reuse source bindings.
inherited, err := m.RestoreSandbox(ctx, "worker:snap", "inherited",
    m.WithDangerouslyInheritResources())
if err != nil { return err }

// Select a captured disk.
privateDisk, err := m.RestoreSandbox(ctx, "worker:snap", "private-disk",
    m.WithRestoreConfig(m.RestoreConfig{CapturedVolumes: []string{"/data"}}))
if err != nil { return err }

// Allow missing resources.
partial, err := m.RestoreSandbox(ctx, "worker:snap", "partial",
    m.WithAllowMissingResources())
if err != nil { return err }
```

```bash CLI
# Reuse source bindings.
msb snap restore worker:snap --name inherited --dangerously-inherit-resources

# Select a captured disk.
msb snap restore worker:snap --name private-disk -v /data

# Allow missing resources.
msb snap restore worker:snap --name partial --allow-missing-resources
```
</CodeGroup>

Allowing missing resources does not reuse source bindings or accept changed files; see [Validate mounts](#validate-mounts). Warnings remain visible even when progress output is suppressed.

Direct forking keeps its existing behavior for unavailable resources. Disk-only restore boots fresh rather than resuming open filesystem handles.

</Accordion>

### Validate mounts

Full restore and forking validate captured filesystem identities. Strict is the default; relaxed mode accepts supported differences and reports warnings:

<CodeGroup>
```typescript TypeScript
const child = await Sandbox.restore("worker:snap")
    .name("child")
    .volume("/work", v => v.bind("/host/copy"))
    .externalMountPolicy("relaxed")
    .restore();
```

```rust Rust
use microsandbox::ExternalMountRestorePolicy;

let child = Sandbox::restore("worker:snap")
    .name("child")
    .volume("/work", |v| v.bind("/host/copy"))
    .external_mount_policy(ExternalMountRestorePolicy::Relaxed)
    .restore()
    .await?;
```

```python Python
from microsandbox import Sandbox, Volume

child = await Sandbox.restore(
    "worker:snap",
    name="child",
    volumes={"/work": Volume.bind("/host/copy")},
    external_mount_policy="relaxed",
)
```

```go Go
child, err := m.RestoreSandbox(ctx, "worker:snap", "child",
    m.WithRestoreConfig(m.RestoreConfig{
        Volumes: map[string]m.MountConfig{
            "/work": m.Mount.Bind("/host/copy", m.MountOptions{}),
        },
    }),
    m.WithExternalMountPolicy(m.ExternalMountRelaxed))
if err != nil { return err }
```

```bash CLI
msb snap restore worker:snap --name child \
    -v /host/copy:/work --external-mount-policy relaxed
```
</CodeGroup>

Relaxed mode accepts supported changes to supplied filesystem objects. It still checks integrity and requires access to the supplied paths. It does not recreate missing files or allow missing resources.

<Accordion title="Open files and cached data">

Full snapshots retain clean cache pages, so reads may return captured bytes before reaching the host. Strict validation checks identity at restore time; it does not lock files against later edits.

External files deleted while still open, and special files, can prevent capture. Owned directories support deleted open regular files. Restore warnings identify inaccessible or changed objects; see your [SDK reference](#reference) for details.

</Accordion>

## Manage snapshots

These operations apply to disk and full snapshots. Cloud supports basic disk snapshot management; groups, archives, verification, and disk maintenance below are local-only.

### Restore progress

Track local disk or full restore through image preparation, memory preparation, and activation. Wait for the final result before using the sandbox:

<CodeGroup>
```typescript TypeScript
const creation = await Sandbox.restore("worker:snap")
    .name("resumed")
    .restoreWithProgress();

for await (const event of creation) {
    console.log(event);
}

const resumed = await creation.awaitSandbox();
```

```rust Rust
let (mut progress, task) = Sandbox::restore("worker:snap")
    .name("resumed")
    .restore_with_progress()?;

while let Some(event) = progress.recv().await {
    eprintln!("{event:?}");
}

let resumed = task.await??;
```

```python Python
creation = Sandbox.restore_with_progress(
    "worker:snap",
    name="resumed",
)

async for event in creation.progress:
    print(event)

resumed = await creation.result()
```

```go Go
events, results := m.RestoreSandboxWithProgress(
    ctx, "worker:snap", "resumed",
)

for event := range events {
    fmt.Println(event)
}

result := <-results
if result.Err != nil { return result.Err }
resumed := result.Sandbox
```

```bash CLI
# Restore shows progress by default.
msb snap restore worker:snap --name resumed
```
</CodeGroup>

Some stages have no percentage. Memory preparation has no fixed timeout; activation has a separate timeout. Set an overall deadline or use SDK cancellation when needed.

### Manage saved snapshots

List snapshots, inspect one, or remove it:

<CodeGroup>
```typescript TypeScript
const snapshots = await Snapshot.list();

const saved = await Snapshot.open("baseline:ready");
console.log(saved.digest);

await Snapshot.remove("baseline:ready");
```

```rust Rust
let snapshots = Snapshot::list().await?;

let saved = Snapshot::open("baseline:ready").await?;
println!("{}", saved.digest());

Snapshot::remove("baseline:ready", false).await?;
```

```python Python
snapshots = await Snapshot.list()

saved = await Snapshot.open("baseline:ready")
print(saved.digest)

await Snapshot.remove("baseline:ready")
```

```go Go
snapshots, err := m.Snapshot.List(ctx)
if err != nil { return err }

saved, err := m.Snapshot.Open(ctx, "baseline:ready")
if err != nil { return err }

fmt.Println(saved.Digest())
err = m.Snapshot.Remove(ctx, "baseline:ready", false)
if err != nil { return err }
```

```bash CLI
msb snap ls
msb snap ls --group baseline
msb snap inspect baseline:ready
msb snap rm baseline:ready
```
</CodeGroup>

### Snapshot groups

Locally, `group` selects its head; `group:member` selects an exact snapshot:

<CodeGroup>
```typescript TypeScript
await Snapshot.builder("cp01")
    .fromSandbox("baseline")
    .group("work")
    .create();

await Snapshot.builder("cp02")
    .fromSandbox("baseline")
    .group("work")
    .create();

const latest = await Sandbox.restore("work")
    .name("latest")
    .restore();

const earlier = await Sandbox.restore("work:cp01")
    .name("earlier")
    .restore();

await Snapshot.groupHead("work");

await Snapshot.groupHead("work:cp01");
```

```rust Rust
Snapshot::builder("cp01")
    .from_sandbox("baseline")
    .group("work")
    .create()
    .await?;

Snapshot::builder("cp02")
    .from_sandbox("baseline")
    .group("work")
    .create()
    .await?;

let latest = Sandbox::restore("work")
    .name("latest")
    .restore()
    .await?;

let earlier = Sandbox::restore("work:cp01")
    .name("earlier")
    .restore()
    .await?;

Snapshot::group_head("work").await?;

Snapshot::group_head("work:cp01").await?;
```

```python Python
await Snapshot.create("cp01", from_sandbox="baseline", group="work")

await Snapshot.create("cp02", from_sandbox="baseline", group="work")

latest = await Sandbox.restore("work", name="latest")

earlier = await Sandbox.restore("work:cp01", name="earlier")

await Snapshot.group_head("work")

await Snapshot.group_head("work:cp01")
```

```go Go
var err error

_, err = m.Snapshot.Create(ctx, m.SnapshotCreateOptions{
    Name: "cp01",
    FromSandbox: "baseline",
    Group: "work",
})
if err != nil { return err }

_, err = m.Snapshot.Create(ctx, m.SnapshotCreateOptions{
    Name: "cp02",
    FromSandbox: "baseline",
    Group: "work",
})
if err != nil { return err }

latest, err := m.RestoreSandbox(ctx, "work", "latest")
if err != nil { return err }

earlier, err := m.RestoreSandbox(ctx, "work:cp01", "earlier")
if err != nil { return err }

_, err = m.Snapshot.GroupHead(ctx, "work")
if err != nil { return err }

_, err = m.Snapshot.GroupHead(ctx, "work:cp01")
if err != nil { return err }
```

```bash CLI
msb snap create cp01 --sandbox baseline --group work
msb snap create cp02 --sandbox baseline --group work
msb snap restore work --name latest
msb snap restore work:cp01 --name earlier
msb snap head work           # Show the selected snapshot ID
msb snap head work:cp01      # Explicitly select an earlier checkpoint
```
</CodeGroup>

The first capture sets the head. Later captures or imports advance it only with proven ancestry; older or divergent snapshots leave it unchanged. Select another head before removing the current one if other members remain.

<Accordion title="Import into a group">

Imports without a group create a new one. Select an imported head explicitly when needed:

<CodeGroup>
```typescript TypeScript
await Snapshot.loadWithOptions("snap.msb", { group: "work" });

await Snapshot.loadWithOptions("experiment.msb", {
    group: "work",
    setHead: true,
});
```

```rust Rust
Snapshot::load_with_options(
    std::path::Path::new("snap.msb"),
    microsandbox::snapshot::LoadOpts {
        group: Some("work".into()),
        ..Default::default()
    },
).await?;

Snapshot::load_with_options(
    std::path::Path::new("experiment.msb"),
    microsandbox::snapshot::LoadOpts {
        group: Some("work".into()),
        set_head: true,
        ..Default::default()
    },
).await?;
```

```python Python
await Snapshot.load("snap.msb", group="work")

await Snapshot.load("experiment.msb", group="work", set_head=True)
```

```go Go
var err error

_, err = m.Snapshot.LoadWithOptions(ctx, "snap.msb",
    m.SnapshotLoadOptions{Group: "work"},
)
if err != nil { return err }

_, err = m.Snapshot.LoadWithOptions(ctx, "experiment.msb",
    m.SnapshotLoadOptions{Group: "work", SetHead: true},
)
if err != nil { return err }
```

```bash CLI
msb snap import snap.msb --group work
msb snap import experiment.msb --group work --set-head
```
</CodeGroup>

Identical members can be reused; conflicting IDs or names fail. Publication never overwrites an existing member. Inspect the group before retrying an interrupted import.

</Accordion>

## Move snapshots

Archives let you move local disk or full snapshots between machines.

### Export and restore

Copy either snapshot type to another machine as a `.msb` archive. Include the image for offline restore:

<CodeGroup>
```typescript TypeScript
await Snapshot.save("baseline:ready", "ready.msb", { withImage: true });
```

```rust Rust
Snapshot::save("baseline:ready",
    std::path::Path::new("ready.msb"),
    microsandbox::snapshot::SaveOpts {
        with_image: true,
        ..Default::default()
    },
).await?;
```

```python Python
await Snapshot.save("baseline:ready", "ready.msb", with_image=True)
```

```go Go
var err error

err = m.Snapshot.Save(ctx, "baseline:ready", "ready.msb",
    m.SnapshotSaveOptions{WithImage: true},
)
if err != nil { return err }
```

```bash CLI
msb snap export baseline:ready --output ready.msb --with-image
```
</CodeGroup>

On the destination:

<CodeGroup>
```typescript TypeScript
const worker = await Sandbox.restore("./ready.msb")
    .name("worker")
    .restore();
```

```rust Rust
let worker = Sandbox::restore("./ready.msb")
    .name("worker")
    .restore()
    .await?;
```

```python Python
worker = await Sandbox.restore("./ready.msb", name="worker")
```

```go Go
worker, err := m.RestoreSandbox(ctx, "./ready.msb", "worker")
if err != nil { return err }
```

```bash CLI
msb snap restore ./ready.msb --name worker
```
</CodeGroup>

The archive keeps its type: disk boots fresh; full resumes execution. Existing `.tar` and `.tar.zst` archives also work. Full compatibility and owned-directory platform restrictions still apply.

### Import archives

Load a base and dependent archives together; order does not matter:

<CodeGroup>
```typescript TypeScript
const handles = await Snapshot.loadMany(
    ["changes.msb", "base.msb"],
    { group: "received", dest: "/mnt/snapshots" },
);
```

```rust Rust
let archives = vec!["changes.msb".into(), "base.msb".into()];

let handles = Snapshot::load_many(
    &archives,
    microsandbox::snapshot::LoadOpts {
        group: Some("received".into()),
        dest: Some("/mnt/snapshots".into()),
        ..Default::default()
    },
).await?;
```

```python Python
handles = await Snapshot.load_many(
    ["changes.msb", "base.msb"],
    group="received",
    dest="/mnt/snapshots",
)
```

```go Go
handles, err := m.Snapshot.LoadMany(ctx,
    []string{"changes.msb", "base.msb"},
    m.SnapshotLoadOptions{Group: "received", Dest: "/mnt/snapshots"},
)
if err != nil { return err }
```

```bash CLI
msb snap import changes.msb base.msb --group received --dest /mnt/snapshots
```
</CodeGroup>

Dependencies come from the batch or matching members of the named group. Supply an explicit base for other dependencies; unrelated groups are not searched. All members are validated before publication. Divergent tips require choosing a head; forcing an ambiguous head fails.

<span id="export-changes-and-compact-a-disk-chain" />

### Export changes

Export only disk layers and full-snapshot RAM objects not supplied by an exact base:

<CodeGroup>
```typescript TypeScript
await Snapshot.builder("v2")
    .fromSandbox("worker")
    .full()
    .create();

await Snapshot.save("worker:v2", "changes.msb", {
    since: "worker:v1",
});
```

```rust Rust
Snapshot::builder("v2")
    .from_sandbox("worker")
    .full()
    .create()
    .await?;

Snapshot::save("worker:v2",
    std::path::Path::new("changes.msb"),
    microsandbox::snapshot::SaveOpts {
        since: Some("worker:v1".into()),
        ..Default::default()
    },
).await?;
```

```python Python
await Snapshot.create("v2", from_sandbox="worker", full=True)

await Snapshot.save(
    "worker:v2",
    "changes.msb",
    since="worker:v1",
)
```

```go Go
var err error

_, err = m.Snapshot.Create(ctx, m.SnapshotCreateOptions{
    Name: "v2",
    FromSandbox: "worker",
    Full: true,
})
if err != nil { return err }

err = m.Snapshot.Save(ctx, "worker:v2", "changes.msb",
    m.SnapshotSaveOptions{Since: "worker:v1"},
)
if err != nil { return err }
```

```bash CLI
msb snap create v2 --sandbox worker --full
msb snap export worker:v2 --output changes.msb --since worker:v1
```
</CodeGroup>

The root and every shared owned disk must match the base’s physical prefix. New owned disks are complete. Owned directory namespaces and full execution maps remain complete; unchanged payloads may come from the base.

Import or restore with that base:

<CodeGroup>
```typescript TypeScript
await Snapshot.loadWithOptions("changes.msb", {
    group: "imported",
    base: "worker:v1",
});

const child = await Sandbox.restore("changes.msb")
    .name("child")
    .snapshotBase("worker:v1")
    .restore();
```

```rust Rust
Snapshot::load_with_options(
    std::path::Path::new("changes.msb"),
    microsandbox::snapshot::LoadOpts {
        group: Some("imported".into()),
        base: Some("worker:v1".into()),
        ..Default::default()
    },
).await?;

let child = Sandbox::restore("changes.msb")
    .name("child")
    .snapshot_base("worker:v1")
    .restore()
    .await?;
```

```python Python
await Snapshot.load(
    "changes.msb",
    group="imported",
    base="worker:v1",
)

child = await Sandbox.restore(
    "changes.msb",
    name="child",
    snapshot_base="worker:v1",
)
```

```go Go
var err error

_, err = m.Snapshot.LoadWithOptions(ctx, "changes.msb",
    m.SnapshotLoadOptions{Group: "imported", Base: "worker:v1"},
)
if err != nil { return err }

child, err := m.RestoreSandbox(ctx, "changes.msb", "child",
    m.WithSnapshotBase("worker:v1"),
)
if err != nil { return err }
```

```bash CLI
msb snap import changes.msb --base worker:v1 --group imported
msb snap restore changes.msb --name child --snapshot-base worker:v1
```
</CodeGroup>

Missing dependencies fail before publication or execution. Once loaded or restored, the result owns its files and no longer depends on the base.

<Accordion title="Other export options">

The last-layers option exports only the newest root layers, keeping owned disks and required RAM complete. Do not combine it with since-base export, or use either with parent-chain or whole-group export.

After compaction, export a new standalone baseline. Independent stopped captures can also get new layer identities despite identical bytes; use a standalone export when the base no longer matches.

</Accordion>

### Capture to an archive

Capture directly to an archive without installing a snapshot locally. Disk is the default; add the full option for execution state:

<CodeGroup>
```typescript TypeScript
import { Snapshot } from "microsandbox";

const archive = await Snapshot.builder("deps")
    .fromSandbox("baseline")
    .createArchive("/tmp/deps.msb");
```

```rust Rust
use microsandbox::Snapshot;

let archive = Snapshot::builder("deps")
    .from_sandbox("baseline")
    .create_archive("/tmp/deps.msb", false)
    .await?;
```

```python Python
from microsandbox import Snapshot

archive = await Snapshot.create_archive(
    "deps",
    "/tmp/deps.msb",
    from_sandbox="baseline",
)
```

```go Go
archive, err := m.Snapshot.CreateArchive(ctx, m.SnapshotArchiveOptions{
    SnapshotCreateOptions: m.SnapshotCreateOptions{
        Name:        "deps",
        FromSandbox: "baseline",
    },
    ArchivePath: "/tmp/deps.msb",
})
```

```bash CLI
msb snap create deps \
    --sandbox baseline \
    -o /tmp/deps.msb
```
</CodeGroup>

Direct capture pins the image but does not bundle its cache. For offline use, create an installed snapshot and [export with the image](#export-and-restore). Restore the archive directly using the same restore APIs.

## Compact disks

Merge sealed disk layers in a running or fully stopped local sandbox. This affects disk storage, including disks in full snapshots, not memory. Preview before applying:

<CodeGroup>
```typescript TypeScript
const worker = await Sandbox.get("worker");

const plan = await worker.compact({ layers: 3, dryRun: true });

const result = await worker.compact({ layers: 3 });
```

```rust Rust
let worker = Sandbox::get("worker").await?;

let plan = worker.compact().layers(3).dry_run().await?;

let result = worker.compact().layers(3).apply().await?;
```

```python Python
worker = await Sandbox.get("worker")

plan = await worker.compact(layers=3, dry_run=True)

result = await worker.compact(layers=3)
```

```go Go
worker, err := m.GetSandbox(ctx, "worker")
if err != nil { return err }

layers := uint32(3)
plan, err := worker.Compact(ctx, m.DiskCompactionOptions{
    Layers: &layers,
    DryRun: true,
})
if err != nil { return err }

result, err := worker.Compact(ctx, m.DiskCompactionOptions{Layers: &layers})
if err != nil { return err }
```

```bash CLI
msb modify worker --compact --layers 3 --dry-run
msb modify worker --compact --layers 3
```
</CodeGroup>

The default covers the root and owned data disks, excluding named/external volumes and directories. The layer limit includes the base but never the writable head; it must be at least two. Omit it to merge all sealed layers. Existing snapshots remain valid and retain storage until removed.

<Warning>
Each disk commits separately. If compaction fails, a running VM may stay paused. Restart to recover from disk journals; do not assume rollback or blindly retry.
</Warning>

Use the [SDK references](#reference) for disk selection and result fields. Do not combine compaction with unrelated modifications.

## Verify integrity

Disk content hashing is opt-in. Record hashes during capture, then verify when receiving or checking a snapshot:

<CodeGroup>
```typescript TypeScript
import { Snapshot } from "microsandbox";

const snap = await Snapshot.builder("deps")
    .fromSandbox("baseline")
    .recordIntegrity()
    .create();

const report = await snap.verify();
```

```rust Rust
use microsandbox::Snapshot;

let snap = Snapshot::builder("deps")
    .from_sandbox("baseline")
    .record_integrity()
    .create()
    .await?;

let report = snap.verify().await?;
```

```python Python
from microsandbox import Snapshot

snap = await Snapshot.create(
    "deps",
    from_sandbox="baseline",
    record_integrity=True,
)

report = await snap.verify()
```

```go Go
snap, err := m.Snapshot.Create(ctx,
    m.SnapshotCreateOptions{
        Name:            "deps",
        FromSandbox:     "baseline",
        RecordIntegrity: true,
    },
)

report, err := snap.Verify(ctx)
```

```bash CLI
msb snap create deps --sandbox baseline --integrity
msb snap verify baseline:deps
msb snap inspect baseline:deps --verify
```
</CodeGroup>

Without hashes, size and structure checks cannot detect same-length disk corruption. Save and load preserve recorded integrity but do not scan it automatically. Full-snapshot RAM has separate content-addressed checks; fork RAM is not hashed.

<Accordion title="Full snapshots and forks">

<CodeGroup>
```typescript TypeScript
await Snapshot.builder("saved")
    .fromSandbox("worker")
    .full()
    .recordIntegrity()
    .create();

const source = await Sandbox.get("worker");
const child = await source.fork("child", { recordIntegrity: true });
```

```rust Rust
Snapshot::builder("saved")
    .from_sandbox("worker")
    .full()
    .record_integrity()
    .create()
    .await?;

let source = Sandbox::get("worker").await?;
let child = source.fork("child").record_integrity().fork().await?;
```

```python Python
await Snapshot.create(
    "saved",
    from_sandbox="worker",
    full=True,
    record_integrity=True,
)

source = await Sandbox.get("worker")
child = await source.fork("child", record_integrity=True)
```

```go Go
var err error

_, err = m.Snapshot.Create(ctx, m.SnapshotCreateOptions{
    Name: "saved",
    FromSandbox: "worker",
    Full: true,
    RecordIntegrity: true,
})
if err != nil { return err }

source, err := m.GetSandbox(ctx, "worker")
if err != nil { return err }

child, err := source.Fork(ctx, "child", m.WithForkIntegrity())
if err != nil { return err }
```

```bash CLI
msb snap create saved --sandbox worker --full --integrity
msb fork worker --name child --integrity
```
</CodeGroup>

</Accordion>

## Troubleshooting

| Problem | What to check |
| --- | --- |
| Cloud capture is rejected | Stop the source and use disk mode. Full snapshots and local archive options are unsupported. |
| Full restore rejects resource settings | Keep the captured CPU and memory settings, or restore with `--disk-only`. A full snapshot with a tmpfs root must resume execution. |
| A restored app loses its connection | Reconnect clients and retry application connections; live host connections are not captured. |
| Capture fails after saving a snapshot | Inspect the saved snapshot before retrying. The error may mean capture succeeded but the source could not resume. |
| A local snapshot is missing from the list | Run `msb snap reindex` to rebuild the local index. |


## Reference

See each SDK reference for setup, imports, optional controls, and result fields.

- **SDKs:** [TypeScript](/sdk/typescript/snapshots), [Rust](/sdk/rust/snapshots), [Python](/sdk/python/snapshots), and [Go](/sdk/go/snapshots).
- **CLI:** Run `msb snap --help` or `msb snap restore --help` for all options.
