Back to Microsandbox

Snapshots

docs/sandboxes/snapshots.mdx

0.4.48.9 KB
Original Source

A snapshot captures a stopped sandbox's writable upper layer as a self-describing, content-addressed directory on disk. You can move it between machines with scp, archive it as .tar.zst, and use it to boot fresh sandboxes that start from the captured state instead of a clean image.

The artifact is a directory containing manifest.json (canonical JSON, identity is the SHA-256 of these bytes) plus the captured upper.ext4. The directory is the snapshot — there is no hidden daemon state. A local DB indexes known snapshots for fast queries, but it's a rebuildable cache; deleting it loses nothing.

<Note> Snapshots are **disk-only and stopped-only**: they capture the writable filesystem layer of a sandbox that's not currently running. Live snapshots (memory + CPU state) and qcow2-backed checkpoint chains are future work. </Note>

What gets captured

CapturedNot captured
upper.ext4 — the sandbox's writable overlayMemory contents
Pinned image reference + manifest digestCPU registers / running processes
fstype, format, optional labelsNetwork state
Optional content-integrity hashOpen file handles

Booting from a snapshot is a cold boot of a fresh VM that reuses the captured upper layer as its starting filesystem. The image (lower layer) is re-resolved by digest from the local OCI cache, or re-pulled from the registry if missing.

Quick start

Most users will reach for the CLI first:

bash
# 1. Run something, install state, then stop it
msb run --name baseline --detach python:3.12 -- sleep 3600
msb exec baseline -- pip install requests
msb stop baseline

# 2. Snapshot the stopped sandbox
msb snapshot create after-pip-install --from baseline

# 3. Boot a fresh sandbox from the snapshot
msb run --name worker --snapshot after-pip-install -- python -c "import requests; print(requests.__version__)"

The snapshot lives at ~/.microsandbox/snapshots/after-pip-install/. Move that directory to another machine with scp and run the same msb run --snapshot after-pip-install there — as long as the image is cached locally or pullable from a registry, it just works.

Snapshot a stopped sandbox

The Rust SDK exposes two methods on the looked-up sandbox handle so you don't have to import a destination enum:

<CodeGroup> ```rust Rust use microsandbox::Sandbox;

// Resolve a handle for an existing sandbox (must be stopped). let h = Sandbox::get("baseline").await?;

// Bare name → resolves under ~/.microsandbox/snapshots/<name>/ let snap = h.snapshot("after-pip-install").await?;

// Or write to an explicit path let snap = h.snapshot_to("/tmp/snaps/v1").await?;

println!("{}", snap.digest()); // sha256:...


```bash CLI
msb snapshot create after-pip-install --from baseline
msb snapshot create ./snaps/v1     --from baseline   # explicit path
msb snapshot create after-pip-install --from baseline --label stage=ready
typescript
import { Sandbox } from "microsandbox";

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

// Bare name resolves under ~/.microsandbox/snapshots/<name>/
const snap = await h.snapshot("after-pip-install");

// Or write to an explicit path
const snap2 = await h.snapshotTo("/tmp/snaps/v1");

console.log(snap.digest); // sha256:...
python
from microsandbox import Sandbox

h = await Sandbox.get("baseline")

# Bare name resolves under ~/.microsandbox/snapshots/<name>/
snap = await h.snapshot("after-pip-install")

# Or write to an explicit path
snap2 = await h.snapshot_to("/tmp/snaps/v1")

print(snap.digest)  # sha256:...
</CodeGroup>

The sandbox must be stopped (or crashed). Running sandboxes are rejected with MicrosandboxError::SnapshotSandboxRunning.

Boot from a snapshot

The sandbox builder gains a from_snapshot method that's mutually exclusive with the image source — the snapshot already pins the image:

<CodeGroup> ```rust Rust use microsandbox::Sandbox;

let sb = Sandbox::builder("worker") .from_snapshot("after-pip-install") .create() .await?;


```bash CLI
msb run --name worker --snapshot after-pip-install -- python -V
typescript
import { Sandbox } from "microsandbox";

const sb = await Sandbox.builder("worker")
    .fromSnapshot("after-pip-install")
    .create();
python
from microsandbox import Sandbox

# `snapshot=` is a peer of `image=` and mutually exclusive with it
sb = await Sandbox.create("worker", snapshot="after-pip-install")
</CodeGroup>

The boot path:

  1. Opens the snapshot artifact, validates its manifest, and confirms the upper file's apparent size matches.
  2. Looks up the pinned image manifest digest in the local OCI cache. If missing, re-pulls and verifies the registry returns the same digest.
  3. Copies the captured upper.ext4 into the new sandbox's directory using clonefile on macOS APFS or FICLONE on btrfs/XFS (zero-copy COW), with a sparse-aware fallback on ext4. The new sandbox boots immediately.

List, inspect, and remove

<CodeGroup> ```bash CLI msb snapshot ls msb snapshot inspect after-pip-install msb snapshot rm after-pip-install msb snapshot rm after-pip-install --force # also if it has indexed children

Rebuild the index from artifacts on disk

msb snapshot reindex


```rust Rust
use microsandbox::Snapshot;

let all = Snapshot::list().await?;                  // indexed snapshots
let h = Snapshot::get("after-pip-install").await?;  // by name, digest, or path
println!("{} ({})", h.name().unwrap_or("-"), h.digest());

Snapshot::remove("after-pip-install", false).await?;
Snapshot::reindex("~/.microsandbox/snapshots").await?;
</CodeGroup>

Snapshot::open(path_or_name) is the cheap metadata path used by inspect and the boot flow — it validates the manifest and checks the upper file's size, but does not read the file's contents. That's by design: the load-bearing local operation has to be fast.

Move snapshots between machines

The artifact is just a directory. Three valid distribution moves:

bash
# (a) scp the directory. Requires the image to be cached or pullable on target.
scp -r ~/.microsandbox/snapshots/after-pip-install \
    other-host:~/.microsandbox/snapshots/

# (b) Bundle into a .tar.zst archive
msb snapshot export after-pip-install /tmp/snap.tar.zst
scp /tmp/snap.tar.zst other-host:
ssh other-host msb snapshot import /tmp/snap.tar.zst

# (c) Fully offline — bundle the OCI image cache too
msb snapshot export after-pip-install /tmp/snap.tar.zst --with-image
ssh other-host msb snapshot import /tmp/snap.tar.zst   # no network needed

Archives default to .tar.zst because zstd collapses sparse upper-layer zero runs cheaply. Pass --plain-tar if you need a plain .tar. Both formats are accepted on import via magic-byte detection.

Integrity verification

By default, snapshot creation does not hash the upper file. The artifact carries its size and the OCI image's manifest digest, which together catch corruption that would prevent it from booting. Hashing 4 GiB of mostly-zero sparse bytes on every inspect would dominate latency and add no real safety for a file we just wrote on the same disk.

Opt in to a content-integrity hash when crossing trust boundaries:

bash
# Compute and record an integrity hash at create time
msb snapshot create after-pip-install --from baseline --integrity

# Verify a snapshot's recorded integrity on demand
msb snapshot verify after-pip-install
msb snapshot inspect after-pip-install --verify

msb snapshot export automatically populates the integrity hash before bundling, and msb snapshot import verifies it on the way in. The local index path stays fast; the cross-machine path stays trustworthy.

Use cases

  • Reusable build state. Install dependencies once, snapshot, then msb run --snapshot ... repeatedly without paying the install cost. Common pattern for CI, agent workloads, and reproducible dev environments.
  • Portable scratch state. Capture a sandbox after a long setup, hand the artifact to a teammate or push it to shared storage, and let them boot from the same starting point.
  • Local fork-by-copy. Multiple sandboxes from one snapshot are independent; each copy of the upper layer diverges on its own.
  • Disaster recovery. Snapshot a sandbox before a risky migration; if it goes wrong, msb rm the broken one and msb run --snapshot from the pre-migration artifact.

What's not yet supported

  • Live (running) snapshots. The sandbox must be stopped first. Live capture requires an in-guest filesystem freeze and memory checkpointing that's tracked as future work.
  • qcow2 backing chains. Snapshots always copy the upper layer today. The manifest's format and parent fields are reserved so qcow2 chains can land additively without a schema break.
  • Push/pull from a registry. The artifact format is registry-shaped on purpose, but transport tooling beyond scp and tar.zst is a separate piece of work.
  • Snapshotting DiskImage volumes. The same mechanism would apply (reflink the disk file), but is currently out of scope.