docs/sdk/rust/snapshots.mdx
Create and manage disk-only snapshots of stopped sandboxes. See Snapshots for usage and lifecycle concepts.
fn builder(name: impl Into<String>) -> SnapshotBuilder
Start configuring a new snapshot named name, resolved under the default snapshots directory (~/.microsandbox/snapshots/<name>/) or under dest_dir() when set. The source sandbox is set with from_sandbox(), which is required; the other setters cover labels and whether to record content integrity before capturing. See SnapshotBuilder for all options.
let snap = Snapshot::builder("baseline")
.from_sandbox("api")
.create()
.await?;
async fn create(config: SnapshotConfig) -> MicrosandboxResult<Snapshot>
Create a snapshot artifact from a stopped sandbox. Writes the snapshot.json descriptor and the captured upper.ext4 into the artifact directory atomically (the descriptor 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("baseline").from_sandbox("api").build()?
).await?;
async fn open(path_or_name: impl AsRef<str>) -> MicrosandboxResult<Snapshot>
let snap = Snapshot::open("baseline").await?;
println!("{}", snap.manifest().image.reference);
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.
async fn get(name_or_digest: &str) -> MicrosandboxResult<SnapshotHandle>
let h = Snapshot::get("after-pip-install").await?;
println!("{} from {}", h.digest(), h.image_ref());
Look up a lightweight SnapshotHandle in the local index by name, digest (sha256:/sha512: prefix), or path.
async fn list() -> MicrosandboxResult<Vec<SnapshotHandle>>
for h in Snapshot::list().await? {
println!("{:?} - {}", h.name(), h.digest());
}
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.
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 snapshot.json) and malformed artifacts.
async fn remove(path_or_name: &str, force: bool) -> MicrosandboxResult<()>
Snapshot::remove("after-pip-install", false).await?;
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.
async fn reindex(dir: impl AsRef<Path>) -> MicrosandboxResult<usize>
let n = Snapshot::reindex("/data/snapshots").await?;
println!("indexed {n} snapshots");
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 save(name_or_path: &str, out: &Path, opts: SaveOpts) -> MicrosandboxResult<()>
Bundle a snapshot into a .tar.zst archive (or plain .tar) at out. Recorded payload integrity is preserved but not executed implicitly; call verify() when an independent content scan is part of your workflow. See SaveOpts to also include ancestors and the OCI image cache.
use microsandbox::snapshot::SaveOpts;
use std::path::Path;
Snapshot::save(
"baseline",
Path::new("/tmp/baseline.tar.zst"),
SaveOpts { with_parents: true, with_image: true, ..Default::default() },
).await?;
async fn load(archive_path: &Path, dest: Option<&Path>) -> MicrosandboxResult<SnapshotHandle>
use std::path::Path;
let h = Snapshot::load(Path::new("/tmp/baseline.tar.zst"), None).await?;
println!("loaded {}", h.digest());
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. Structural and archive-entry checks remain mandatory, while recorded payload integrity is preserved for explicit verify(). Returns a handle for the head snapshot.
use std::path::Path;
let h = Snapshot::load(Path::new("/tmp/baseline.tar.zst"), None).await?;
println!("loaded {}", 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 snapshot.json descriptor and the captured upper file.
fn manifest(&self) -> &Manifest
let snap = Snapshot::open("baseline").await?;
let m = snap.manifest();
println!("{} @ {}", m.image.reference, m.image.manifest_digest);
The parsed Manifest: schema, format, fstype, image reference, parent, creation time, labels, and upper-layer metadata.
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>
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"),
}
Recompute the upper layer's recorded content integrity and compare it against the descriptor. Current BLAKE3 Merkle integrity skips known all-hole subtrees and hashes allocated leaves in batches. Released SHA algorithms retain their exact verifier and may still cost O(logical size). Returns NotRecorded without reading payload contents when the descriptor has integrity: null; errors with SnapshotIntegrity on mismatch.
A snapshot handle backed by the local index.
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 scope(&self) -> SnapshotScope
Snapshot payload scope: SnapshotScope::Disk for a disk-only snapshot, Resumable once resumable snapshots land. Always Disk today.
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>
let h = Snapshot::get("baseline").await?;
let snap = h.open().await?;
snap.verify().await?;
Open the underlying artifact metadata, upgrading this lightweight handle to a full Snapshot. Equivalent to Snapshot::open(self.path()).
async fn remove(&self, force: bool) -> MicrosandboxResult<()>
let h = Snapshot::get("baseline").await?;
h.remove(false).await?;
Remove this snapshot. Delegates to Snapshot::remove(self.digest(), force).
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
let sb = Sandbox::builder("api-restored")
.from_snapshot("after-pip-install")
.create()
.await?;
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 structurally opened at create() time; persistent payload integrity is checked only through explicit Snapshot::verify().
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. To place the artifact elsewhere, use Snapshot::save() / Snapshot::load() or move the self-contained artifact directory.
async fn snapshot_to(&self, path: impl AsRef<Path>) -> MicrosandboxResult<Snapshot>
let h = Sandbox::get("api").await?;
h.stop().await?;
let snap = h.snapshot_to("/data/snapshots/baseline").await?;
Builder for snapshot configuration.
fn from_sandbox(self, source_sandbox: impl Into<String>) -> Self
Set the sandbox to capture. Required; build() and create() fail without it.
fn dest_dir(self, dest_dir: impl Into<PathBuf>) -> Self
Create the artifact under this parent directory instead of the default snapshots store. The artifact directory is dest_dir/<name>; the name stays the snapshot's identity either way.
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 with the same name. Without this, creation fails with SnapshotAlreadyExists if the artifact directory exists.
fn record_integrity(self) -> Self
Compute and record sparse-aware BLAKE3 Merkle integrity during creation. verify() checks it explicitly; ordinary open, boot, save, load, and upgrade preserve the value without adding an independent payload pass.
fn resumable(self) -> Self
Request a resumable snapshot (disk plus VM state). Accepted by the builder, but create() currently fails with Unsupported; resumable snapshots have not landed yet.
fn build(self) -> MicrosandboxResult<SnapshotConfig>
Materialize the SnapshotConfig without creating the snapshot. Errors with InvalidConfig if from_sandbox was not called. 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()?).
Inputs to create a snapshot. A type alias for SnapshotSpec. Usually built via SnapshotBuilder rather than constructed directly.
| Field | Type | Description |
|---|---|---|
| name | String | Bare snapshot name; always the artifact directory's basename |
| dest_dir | Option<PathBuf> | Parent directory for the artifact; None = the default snapshots directory |
| source_sandbox | String | Name of the source sandbox; must be stopped |
| labels | Vec<(String, String)> | User-supplied labels |
| force | bool | Overwrite an existing artifact with the same name |
| record_integrity | bool | Compute and record upper-layer integrity at creation |
| resumable | bool | Request a resumable snapshot; returns an unsupported-feature error today |
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) |
Snapshot payload scope. Parsing accepts every known scope so older runtimes can still list and inspect artifacts they cannot restore; create and restore paths enforce support. Re-exported as microsandbox::snapshot::SnapshotScope.
| Value | Description |
|---|---|
Disk | Disk-only snapshot; captures the writable filesystem state |
Resumable | Reserved for future memory/device-state capture |
Options for Snapshot::save(). Implements Default; SaveOpts::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, serialized as the snapshot.json descriptor (DESCRIPTOR_FILENAME). 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 |
| artifact | String | Artifact kind; always "snapshot" |
| scope | SnapshotScope | Payload scope; only disk snapshots are created today |
| 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 |
|---|---|---|
| Variant | Serialized algorithm | Fields |
| --------- | ---------------------- | -------- |
Sha256 | sha256 | digest |
SparseSha256V1 | msb-sparse-sha256-v1 | digest |
FileMerkleBlake3V1 | msb-file-merkle-blake3-v1 | root, logical_size, leaf_size |