docs/sandboxes/snapshots.mdx
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.
| Captured | Not captured |
|---|---|
upper.ext4 — the sandbox's writable overlay | Memory contents |
| Pinned image reference + manifest digest | CPU registers / running processes |
fstype, format, optional labels | Network state |
| Optional content-integrity hash | Open 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.
Most users will reach for the CLI first:
# 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.
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
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:...
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:...
The sandbox must be stopped (or crashed). Running sandboxes are rejected with MicrosandboxError::SnapshotSandboxRunning.
The sandbox builder gains a from_snapshot method that's mutually exclusive with the image source — the snapshot already pins the image:
let sb = Sandbox::builder("worker") .from_snapshot("after-pip-install") .create() .await?;
```bash CLI
msb run --name worker --snapshot after-pip-install -- python -V
import { Sandbox } from "microsandbox";
const sb = await Sandbox.builder("worker")
.fromSnapshot("after-pip-install")
.create();
from microsandbox import Sandbox
# `snapshot=` is a peer of `image=` and mutually exclusive with it
sb = await Sandbox.create("worker", snapshot="after-pip-install")
The boot path:
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.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?;
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.
The artifact is just a directory. Three valid distribution moves:
# (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.
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:
# 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.
msb run --snapshot ... repeatedly without paying the install cost. Common pattern for CI, agent workloads, and reproducible dev environments.msb rm the broken one and msb run --snapshot from the pre-migration artifact.format and parent fields are reserved so qcow2 chains can land additively without a schema break.scp and tar.zst is a separate piece of work.DiskImage volumes. The same mechanism would apply (reflink the disk file), but is currently out of scope.