docs/sdk/rust/snapshots.mdx
Capture a stopped sandbox's writable upper layer into a self-describing, content-addressed artifact on disk, then list, verify, export, import, or boot a fresh sandbox from it. See Snapshots for concepts and walkthroughs; this page is the Rust SDK reference.
<div className="msb-glance"> <p className="msb-gl"><span className="msb-dot static"></span>Static · Snapshot<span className="msb-ct">10</span></p> <a className="msb-row" href="#snapshotbuilder"><span className="msb-rn">Snapshot::builder()</span><span className="msb-rg">configure a new snapshot</span></a> <a className="msb-row" href="#snapshotcreate"><span className="msb-rn">Snapshot::create()</span><span className="msb-rg">create from a config</span></a> <a className="msb-row" href="#snapshotopen"><span className="msb-rn">Snapshot::open()</span><span className="msb-rg">open an artifact</span></a> <a className="msb-row" href="#snapshotget"><span className="msb-rn">Snapshot::get()</span><span className="msb-rg">handle from the index</span></a> <a className="msb-row" href="#snapshotlist"><span className="msb-rn">Snapshot::list()</span><span className="msb-rg">indexed snapshots</span></a> <a className="msb-row" href="#snapshotlist_dir"><span className="msb-rn">Snapshot::list_dir()</span><span className="msb-rg">artifacts in a directory</span></a> <a className="msb-row" href="#snapshotremove"><span className="msb-rn">Snapshot::remove()</span><span className="msb-rg">delete an artifact</span></a> <a className="msb-row" href="#snapshotreindex"><span className="msb-rn">Snapshot::reindex()</span><span className="msb-rg">rebuild the index</span></a> <a className="msb-row" href="#snapshotexport"><span className="msb-rn">Snapshot::export()</span><span className="msb-rg">bundle to an archive</span></a> <a className="msb-row" href="#snapshotimport"><span className="msb-rn">Snapshot::import()</span><span className="msb-rg">unpack an archive</span></a> <p className="msb-gl"><span className="msb-dot instance"></span>Instance · Snapshot<span className="msb-ct">5</span></p> <a className="msb-row" href="#snap-digest"><span className="msb-rn">snap.digest()</span><span className="msb-rg">content identity</span></a> <a className="msb-row" href="#snap-path"><span className="msb-rn">snap.path()</span><span className="msb-rg">artifact directory</span></a> <a className="msb-row" href="#snap-manifest"><span className="msb-rn">snap.manifest()</span><span className="msb-rg">parsed manifest</span></a> <a className="msb-row" href="#snap-size_bytes"><span className="msb-rn">snap.size_bytes()</span><span className="msb-rg">upper-layer size</span></a> <a className="msb-row" href="#snap-verify"><span className="msb-rn">snap.verify()</span><span className="msb-rg">recheck integrity</span></a> <p className="msb-gl"><span className="msb-dot instance"></span>Instance · SnapshotHandle<span className="msb-ct">10</span></p> <a className="msb-row" href="#h-digest"><span className="msb-rn">h.digest()</span><span className="msb-rg">manifest digest</span></a> <a className="msb-row" href="#h-name"><span className="msb-rn">h.name()</span><span className="msb-rg">name alias</span></a> <a className="msb-row" href="#h-parent_digest"><span className="msb-rn">h.parent_digest()</span><span className="msb-rg">parent snapshot</span></a> <a className="msb-row" href="#h-image_ref"><span className="msb-rn">h.image_ref()</span><span className="msb-rg">source image</span></a> <a className="msb-row" href="#h-format"><span className="msb-rn">h.format()</span><span className="msb-rg">upper format</span></a> <a className="msb-row" href="#h-size_bytes"><span className="msb-rn">h.size_bytes()</span><span className="msb-rg">indexed size</span></a> <a className="msb-row" href="#h-created_at"><span className="msb-rn">h.created_at()</span><span className="msb-rg">creation time</span></a> <a className="msb-row" href="#h-path"><span className="msb-rn">h.path()</span><span className="msb-rg">artifact directory</span></a> <a className="msb-row" href="#h-open"><span className="msb-rn">h.open()</span><span className="msb-rg">read the artifact</span></a> <a className="msb-row" href="#h-remove"><span className="msb-rn">h.remove()</span><span className="msb-rg">delete this snapshot</span></a> <p className="msb-gl"><span className="msb-dot static"></span>Sandbox entry points<span className="msb-ct">3</span></p> <a className="msb-row" href="#from_snapshot"><span className="msb-rn">.from_snapshot()</span><span className="msb-rg">boot from a snapshot</span></a> <a className="msb-row" href="#h-snapshot"><span className="msb-rn">h.snapshot()</span><span className="msb-rg">snapshot a stopped sandbox</span></a> <a className="msb-row" href="#h-snapshot_to"><span className="msb-rn">h.snapshot_to()</span><span className="msb-rg">snapshot to a path</span></a> <p className="msb-gl"><span className="msb-dot builder"></span>Builder · SnapshotBuilder<span className="msb-ct">8</span></p> <div className="msb-chiprow"> <a className="msb-chip" href="#destination">.destination()</a> <a className="msb-chip" href="#name">.name()</a> <a className="msb-chip" href="#path">.path()</a> <a className="msb-chip" href="#label">.label()</a> <a className="msb-chip" href="#force">.force()</a> <a className="msb-chip" href="#record_integrity">.record_integrity()</a> <a className="msb-chip" href="#build">.build()</a> <a className="msb-chip" href="#create">.create()</a> </div> <p className="msb-gl"><span className="msb-dot type"></span>Types</p> <div className="msb-chiprow"> <a className="msb-typepill" href="#snapshothandle">SnapshotHandle</a> <a className="msb-typepill" href="#snapshotconfig">SnapshotConfig</a> <a className="msb-typepill" href="#snapshotdestination">SnapshotDestination</a> <a className="msb-typepill" href="#snapshotformat">SnapshotFormat</a> <a className="msb-typepill" href="#exportopts">ExportOpts</a> <a className="msb-typepill" href="#snapshotverifyreport">SnapshotVerifyReport</a> <a className="msb-typepill" href="#upperverifystatus">UpperVerifyStatus</a> <a className="msb-typepill" href="#manifest">Manifest</a> <a className="msb-typepill" href="#imageref">ImageRef</a> <a className="msb-typepill" href="#upperlayer">UpperLayer</a> <a className="msb-typepill" href="#upperintegrity">UpperIntegrity</a> </div> </div> <Note> Snapshots are **local-only** and **disk-only** today: they capture a sandbox that is stopped or crashed, and every operation runs against the default local backend. Cloud snapshots and qcow2 backing chains are deferred. </Note> <p className="msb-label" id="typical-flow">Typical flow</p>use microsandbox::{Sandbox, Snapshot};
// 1. stop the sandbox you want to capture
let sb = Sandbox::get("api").await?;
sb.stop().await?;
// 2. capture its writable upper layer
let snap = Snapshot::builder("api")
.name("after-pip-install") // bare name in the default dir
.record_integrity() // hash the upper layer
.create()
.await?;
println!("{} ({} bytes)", snap.digest(), snap.size_bytes());
// 3. boot a fresh sandbox from it
let restored = Sandbox::builder("api-restored")
.from_snapshot("after-pip-install")
.create()
.await?;
fn builder(source_sandbox: impl Into<String>) -> SnapshotBuilder
Start configuring a new snapshot of source_sandbox. The builder lets you set the destination, labels, and whether to record content integrity before capturing. See SnapshotBuilder for all options.
let snap = Snapshot::builder("api")
.name("baseline")
.create()
.await?;
async fn create(config: SnapshotConfig) -> MicrosandboxResult<Snapshot>
Create a snapshot artifact from a stopped sandbox. Writes manifest.json and the captured upper.ext4 into the destination directory atomically (the manifest is renamed into place last), then best-effort upserts a row into the local index. Index failures are logged but do not fail the call; the artifact is the source of truth. Most callers use the builder's create() instead of constructing a SnapshotConfig by hand.
let snap = Snapshot::create(
Snapshot::builder("api").name("baseline").build()?
).await?;
async fn open(path_or_name: impl AsRef<str>) -> MicrosandboxResult<Snapshot>
Open an existing artifact by path or bare name. Bare names (no path separator, not starting with . or ~) resolve under the default snapshots directory; anything else is treated as a path. This is a fast metadata operation: it verifies the manifest structure, recomputes the manifest digest, and checks that the upper file exists with the recorded size. It does not read the full upper contents; use verify() for that.
let snap = Snapshot::open("baseline").await?;
println!("{}", snap.manifest().image.reference);
async fn get(name_or_digest: &str) -> MicrosandboxResult<SnapshotHandle>
Look up a lightweight SnapshotHandle in the local index by name, digest (sha256:/sha512: prefix), or path.
let h = Snapshot::get("after-pip-install").await?;
println!("{} from {}", h.digest(), h.image_ref());
async fn list() -> MicrosandboxResult<Vec<SnapshotHandle>>
List indexed snapshots from the local DB cache, newest first. External-path artifacts booted by full path aren't in the index and won't appear here; use list_dir to enumerate artifacts on disk directly.
for h in Snapshot::list().await? {
println!("{:?} — {}", h.name(), h.digest());
}
async fn list_dir(dir: impl AsRef<Path>) -> MicrosandboxResult<Vec<Snapshot>>
Walk a directory and parse each subdirectory's manifest. Does not touch the index. Skips entries that don't look like snapshot artifacts (no manifest.json) and malformed artifacts.
let snaps = Snapshot::list_dir("/data/snapshots").await?;
println!("{} artifacts", snaps.len());
async fn remove(path_or_name: &str, force: bool) -> MicrosandboxResult<()>
Remove a snapshot artifact (by digest, name, or path) and its index row. Refuses if the snapshot has indexed children unless force is set. The artifact directory is deleted on success and the parent's child count is decremented.
Snapshot::remove("after-pip-install", false).await?;
async fn reindex(dir: impl AsRef<Path>) -> MicrosandboxResult<usize>
Rebuild the local index from the artifacts in dir. Upserts an index row for every artifact found, then recomputes parent-edge child counts in one pass so the cache stays honest about the current set of artifacts.
let n = Snapshot::reindex("/data/snapshots").await?;
println!("indexed {n} snapshots");
async fn export(name_or_path: &str, out: &Path, opts: ExportOpts) -> MicrosandboxResult<()>
Bundle a snapshot into a .tar.zst archive (or plain .tar) at out. The head snapshot is verified before bundling. The recorded manifest is archived as-is, so create the snapshot with record_integrity() when the archive will cross a trust boundary. See ExportOpts to also include ancestors and the OCI image cache.
use microsandbox::snapshot::ExportOpts;
use std::path::Path;
Snapshot::export(
"baseline",
Path::new("/tmp/baseline.tar.zst"),
ExportOpts { with_parents: true, with_image: true, ..Default::default() },
).await?;
async fn import(archive_path: &Path, dest: Option<&Path>) -> MicrosandboxResult<SnapshotHandle>
Unpack a snapshot archive (.tar.zst or .tar, detected from magic bytes) into the snapshots directory (or dest), routing any bundled image-cache entries into the global cache and registering everything found in the index. Recorded integrity is verified as artifacts land. Returns a handle for the head snapshot.
use std::path::Path;
let h = Snapshot::import(Path::new("/tmp/baseline.tar.zst"), None).await?;
println!("imported {}", h.digest());
Methods on an opened Snapshot artifact.
fn digest(&self) -> &str
Canonical content digest of this snapshot's manifest (sha256:hex). This is the snapshot's identity.
fn path(&self) -> &Path
Path to the artifact directory holding the canonical manifest.json and the captured upper file.
fn manifest(&self) -> &Manifest
The parsed Manifest: schema, format, fstype, image reference, parent, creation time, labels, and upper-layer metadata.
let snap = Snapshot::open("baseline").await?;
let m = snap.manifest();
println!("{} @ {}", m.image.reference, m.image.manifest_digest);
fn size_bytes(&self) -> u64
Apparent size of the captured upper layer in bytes (the ext4 virtual size; sparse on disk).
<p className="msb-label">Returns</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><span className="msb-type">u64</span></div> <div className="msb-param-desc">Upper-layer apparent size in bytes.</div> </div> </div>async fn verify(&self) -> MicrosandboxResult<SnapshotVerifyReport>
Recompute the upper layer's content hash and compare it against the manifest. Walks data extents only, so a multi-GiB sparse file with a little data verifies in milliseconds. Returns NotRecorded when the manifest has no integrity descriptor; errors with SnapshotIntegrity on mismatch.
use microsandbox::snapshot::UpperVerifyStatus;
let snap = Snapshot::open("baseline").await?;
match snap.verify().await?.upper {
UpperVerifyStatus::Verified { algorithm, .. } => println!("ok via {algorithm}"),
UpperVerifyStatus::NotRecorded => println!("no integrity hash recorded"),
}
Accessors and lifecycle on a SnapshotHandle (an index row). Returned by Snapshot::get(), Snapshot::list(), and Snapshot::import().
fn digest(&self) -> &str
Manifest digest (sha256:hex), the canonical identity.
fn name(&self) -> Option<&str>
Name alias, or None for digest-only entries.
fn parent_digest(&self) -> Option<&str>
The parent snapshot's digest, or None for a root. Always None today; populated once chained snapshots land.
fn image_ref(&self) -> &str
Image reference the snapshot was taken from.
fn format(&self) -> SnapshotFormat
On-disk format of the upper layer.
<p className="msb-label">Returns</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><a className="msb-type" href="#snapshotformat">SnapshotFormat</a></div> <div className="msb-param-desc">Upper-layer format (<code>Raw</code> today).</div> </div> </div>fn size_bytes(&self) -> Option<u64>
Apparent size of the upper file at index time, if recorded.
fn created_at(&self) -> chrono::NaiveDateTime
Snapshot creation time, parsed from the manifest.
fn path(&self) -> &Path
Local artifact directory path.
async fn open(&self) -> MicrosandboxResult<Snapshot>
Open the underlying artifact metadata, upgrading this lightweight handle to a full Snapshot. Equivalent to Snapshot::open(self.path()).
let h = Snapshot::get("baseline").await?;
let snap = h.open().await?;
snap.verify().await?;
async fn remove(&self, force: bool) -> MicrosandboxResult<()>
Remove this snapshot. Delegates to Snapshot::remove(self.digest(), force).
let h = Snapshot::get("baseline").await?;
h.remove(false).await?;
Snapshot-related methods that live on the sandbox builder and handle. See Sandbox for the full sandbox API.
fn from_snapshot(self, path_or_name: impl Into<String>) -> Self
SandboxBuilder setter. Boot a fresh sandbox from a snapshot artifact. The snapshot already pins the image reference and digest, so this is mutually exclusive with image() and image_with(). The artifact is opened and its integrity verified at create() time, not here.
let sb = Sandbox::builder("api-restored")
.from_snapshot("after-pip-install")
.create()
.await?;
async fn snapshot(&self, name: &str) -> MicrosandboxResult<Snapshot>
SandboxHandle method. Snapshot this sandbox under a bare name in the default snapshots directory (~/.microsandbox/snapshots/<name>/). The sandbox must be stopped or crashed; running sandboxes are rejected with SnapshotSandboxRunning. Local handles only. For an explicit destination see snapshot_to().
let h = Sandbox::get("api").await?;
h.stop().await?;
let snap = h.snapshot("baseline").await?;
async fn snapshot_to(&self, path: impl AsRef<Path>) -> MicrosandboxResult<Snapshot>
SandboxHandle method. Snapshot this sandbox to an explicit filesystem path. The sandbox must be stopped or crashed. Local handles only. For the common case of writing under the default snapshots directory see snapshot().
let h = Sandbox::get("api").await?;
h.stop().await?;
let snap = h.snapshot_to("/data/snapshots/baseline").await?;
Builder for a SnapshotConfig. Obtained via Snapshot::builder(source_sandbox). A destination is required (name or path); the other setters are optional. Every setter returns Self, so calls chain.
let snap = Snapshot::builder("api")
.name("after-pip-install") // bare name in default dir
.label("stage", "post-deps")
.force() // overwrite if it exists
.record_integrity() // hash the upper layer
.create()
.await?;
fn destination(self, dest: SnapshotDestination) -> Self
Set the artifact destination explicitly. The name and path setters are convenience wrappers over this.
fn name(self, name: impl Into<String>) -> Self
Convenience: use a bare name resolved under the default snapshots directory. Sets the destination to SnapshotDestination::Name.
fn path(self, path: impl Into<PathBuf>) -> Self
Convenience: write the artifact to an explicit path. Sets the destination to SnapshotDestination::Path.
fn label(self, key: impl Into<String>, value: impl Into<String>) -> Self
Add a user label. Can be called multiple times. Labels are sorted by key in the manifest's canonical form.
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>key</code><span className="msb-type">impl Into<String></span></div> <div className="msb-param-desc">Label key.</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>value</code><span className="msb-type">impl Into<String></span></div> <div className="msb-param-desc">Label value.</div> </div> </div>fn force(self) -> Self
Overwrite an existing artifact at the destination. Without this, creation fails with SnapshotAlreadyExists if the destination directory exists.
fn record_integrity(self) -> Self
Compute and record an upper-layer content-integrity hash during creation. Recorded integrity is what verify() checks and what import/export rely on when crossing a trust boundary.
fn build(self) -> MicrosandboxResult<SnapshotConfig>
Materialize the SnapshotConfig without creating the snapshot. Errors with InvalidConfig if no destination was set. For capturing, use create instead; it calls build internally.
async fn create(self) -> MicrosandboxResult<Snapshot>
Build and execute the snapshot in one step. Equivalent to Snapshot::create(self.build()?).
A lightweight handle backed by a local index row. Use open() to read the artifact metadata, and Snapshot::verify() for explicit content verification. All accessors are listed under SnapshotHandle methods.
| Method | Type | Description |
|---|---|---|
| digest() | &str | Manifest digest (sha256:hex) |
| name() | Option<&str> | Name alias; None for digest-only entries |
| parent_digest() | Option<&str> | Parent snapshot digest, or None for a root |
| image_ref() | &str | Source image reference |
| format() | SnapshotFormat | On-disk upper format |
| size_bytes() | Option<u64> | Upper file size at index time |
| created_at() | chrono::NaiveDateTime | Creation time from the manifest |
| path() | &Path | Local artifact directory |
| open() | Result<Snapshot> | Open the underlying artifact |
| remove(force) | Result<()> | Remove this snapshot |
Inputs to create a snapshot. A type alias for SnapshotSpec. Usually built via SnapshotBuilder rather than constructed directly.
| Field | Type | Description |
|---|---|---|
| source_sandbox | String | Name of the source sandbox; must be stopped |
| destination | SnapshotDestination | Where to write the artifact |
| labels | Vec<(String, String)> | User-supplied labels |
| force | bool | Overwrite an existing artifact at the destination |
| record_integrity | bool | Compute and record upper-layer integrity at creation |
Where to place a new snapshot artifact. The builder's name() and path(), and the handle's snapshot() / snapshot_to(), construct this enum internally so callers rarely import it.
| Variant | Fields | Description |
|---|---|---|
Name | String | Bare name resolved under the default snapshots directory |
Path | PathBuf | Explicit absolute or relative path to the artifact directory |
On-disk format of the captured upper layer. Today only Raw is produced; the variant exists so qcow2 chains drop in later without a schema migration.
| Value | Description |
|---|---|
Raw | Raw ext4 image, sparse on disk |
Qcow2 | qcow2 with optional backing chain (future) |
Options for Snapshot::export(). Implements Default; ExportOpts::default() writes the head snapshot only, zstd-compressed.
| Field | Type | Description |
|---|---|---|
| with_parents | bool | Walk the parent chain and include each ancestor in the archive |
| with_image | bool | Bundle the OCI image artifacts (EROFS layers, fsmeta, VMDK descriptor) from the global cache so the archive boots offline |
| plain_tar | bool | Skip zstd compression and write a plain .tar. Default: zstd |
Result of explicit snapshot verification.
| Field | Type | Description |
|---|---|---|
| digest | String | Snapshot manifest digest |
| path | PathBuf | Artifact directory |
| upper | UpperVerifyStatus | Upper-layer content verification result |
Upper-layer content verification result.
| Variant | Fields | Description |
|---|---|---|
NotRecorded | - | No content integrity descriptor was recorded in the manifest |
Verified | - algorithm: String |
digest: String | Recorded integrity matched the computed digest |The snapshot artifact manifest, the source of truth for an artifact. Re-exported as microsandbox::snapshot::Manifest. Its SHA-256 digest over the canonical byte form is the snapshot's identity. Field order is load-bearing (it determines the canonical byte layout) and must not be reordered.
| Field | Type | Description |
|---|---|---|
| schema | u32 | Manifest schema version; readers reject unknown values |
| format | SnapshotFormat | On-disk format of the upper layer |
| fstype | String | Filesystem type inside the upper (e.g. ext4) |
| image | ImageRef | Image the snapshot was taken from |
| parent | Option<String> | Parent snapshot digest, or None for a root |
| created_at | String | RFC 3339 creation timestamp |
| labels | BTreeMap<String, String> | User-supplied labels, sorted by key in canonical form |
| upper | UpperLayer | The captured upper layer |
| source_sandbox | Option<String> | Best-effort name of the source sandbox (informational) |
Reference to the OCI image the snapshot was taken from. Re-exported as microsandbox::snapshot::ImageRef.
| Field | Type | Description |
|---|---|---|
| reference | String | Human-readable image reference (e.g. docker.io/library/python:3.12) |
| manifest_digest | String | Digest of the OCI manifest, in sha256:hex form |
Captured upper-layer file metadata. Re-exported as microsandbox::snapshot::UpperLayer.
| Field | Type | Description |
|---|---|---|
| file | String | Filename inside the artifact directory (e.g. upper.ext4) |
| size_bytes | u64 | Apparent size in bytes (ext4 virtual size; sparse on disk) |
| integrity | Option<UpperIntegrity> | Optional content integrity descriptor; None on local hot paths |
Content integrity descriptor for the captured upper layer.
| Field | Type | Description |
|---|---|---|
| algorithm | String | Digest algorithm name (e.g. msb-sparse-sha256-v1) |
| digest | String | Algorithm output, in sha256:hex form for current algorithms |