Skip to content

Snapshots

Snapshots are a Docker sandbox feature. They let you save a Docker container's state as a named image and spin up new sandboxes from it. The sandbox you get back runs on the standard Docker runtime - same as create({ image: 'ubuntu:22.04' }), just with your pre-built image instead.

A snapshot is a named, reusable image you can launch sandboxes from. AerolVM gives you three ways to make one and a single way to use it.

  • Capture a running sandbox - createSnapshot commits the live container (writable layer + installed packages + entrypoint metadata) into a Docker image and persists a row pointing at it. Useful for "save where I am, give it a name, let me spin clones of this state".
  • Register a pre-built image - registerSnapshot with image records a snapshot row that points at a registry reference (e.g. python:3.12-slim) so the rest of your fleet can address it by a short name.
  • Build from a Dockerfile - registerSnapshot with dockerfileContent (or the registerSnapshotFromImage helper that compiles an Image builder) hands the daemon a Dockerfile, builds it on the host, and stores the resulting content-addressed tag as the snapshot's image.

In all three cases you launch a sandbox from the snapshot by passing its name to create as the image argument - the server resolves the name to the underlying image at create time.

The container's writable layer is committed into a new image and a snapshot row is persisted with that image. The original container keeps running; the snapshot is independent.

const sandbox = await client.create({ image: 'ubuntu:22.04' });
await sandbox.exec(['apt-get', 'update']);
await sandbox.exec(['apt-get', 'install', '-y', 'python3-pip']);
const snapshot = await client.createSnapshot(sandbox.id, 'py-ready');
console.log(snapshot.name, snapshot.image);

Snapshot names are globally unique within a deployment and become the address you pass to create later. Calling createSnapshot twice with the same name on different sandboxes is rejected - pick a versioned name (py-ready-2026-05) if you re-snapshot after changes.

When the image already exists in a registry, skip the build/commit step and just record a row that points at it. The image is not pulled until the first sandbox that references it gets created.

await client.registerSnapshot({
name: 'py-base',
image: 'python:3.12-slim',
cpu: 2,
memoryMB: 4096,
diskGB: 10,
});

The resource hints (cpu, memoryMB, diskGB) are surfaced back when the snapshot is referenced - useful for client UIs that want to render reasonable defaults without hard-coding them.

For images that don't exist in a registry yet, hand the daemon a Dockerfile. The build runs on the host using the configured image builder; the resulting content-addressed tag is what the snapshot row points at.

await client.registerSnapshot({
name: 'data-tools',
dockerfileContent: `
FROM python:3.12-slim
RUN pip install --no-cache-dir pandas duckdb polars
`.trim(),
});

A single-line FROM is a fast-path: the daemon notices the Dockerfile only pins a base image and registers it without running a build. Multi-step Dockerfiles go through the full build pipeline and are content-hashed, so an identical Dockerfile re-registered later hits the build cache.

image and dockerfileContent are mutually exclusive - pass exactly one.

Once a snapshot exists, pass its name where you would normally pass an image. The server resolves the name to the underlying image, inherits the snapshot's resource hints if you don't override them, and runs the create through the same path it would for a bare image reference.

const sandbox = await client.create({
image: 'py-ready',
});

Snapshot resolution happens server-side, so the snapshot name and a registry reference are interchangeable in image. If py-ready is also a valid image reference (it usually isn't - snapshot names are short identifiers, not registry paths), the snapshot wins.

On a single-node deployment, a snapshot lives only on the host that created it - which is fine, because that host runs every sandbox. In cluster mode, a snapshot taken on one node is invisible to the others by default: the local commit produced an image only the originating node holds. Without help, a sandbox launched from that snapshot can only land on the node where the snapshot was taken, and a host failure takes the snapshot with it.

AerolVM ships an optional background push that promotes a snapshot from "local only" to a cluster-distributable AOCR (Aerol OCI Registry) reference. When enabled, the snapshot create path does not change: the API still returns immediately with the local row, and a reconciler pushes the image in the background. Once the push completes, the row's distribution metadata flips to aocr and any node in the cluster can pull the image when scheduling a sandbox from it. Combined with failover.policy = "recreate" (see Durability and Failover), this makes snapshot-backed sandboxes survive owner-node death.

Operators enable it at the daemon level - there is no SDK toggle, because the choice is operational, not per-call:

Env varPurpose
SB_SNAPSHOT_PUSH_ENABLEDGates the entire feature. When false (default), snapshots stay local-only and behave as they always have.
SB_AUTO_IMPORT_CLUSTER_IDTenant namespace under AOCR (cluster/<id>/snapshots/<name>:latest). Reused from the auto-import setup.
SB_AUTO_IMPORT_CLUSTER_PAT_PATHFile holding the bearer token used as the registry password. Re-read on every push, so rotation is just a file write.
SB_MIRROR_PUSH_HOSTPush destination (e.g. aocr.aerol.ai). Falls back to SB_IMAGE_DISTRIBUTION_AOCR_HOST when unset.
SB_SNAPSHOT_PUSH_RECONCILE_INTERVALReconciler tick. Default 5m.
SB_SNAPSHOT_PUSH_MAX_IN_FLIGHTPer-tick fan-out cap. Default 2.
SB_SNAPSHOT_PUSH_TAG_SUFFIXOptional AOCR retention suffix appended to the pushed tag (latest<suffix>), e.g. --ttl-7d or --idle-30d. Empty (default) pushes a plain latest tag the reaper keeps indefinitely — correct for durable failover snapshots. Ephemeral clusters set a short TTL so their snapshots auto-reap. Push and pull use the same suffix.

Each snapshot row carries a pushState field you can read back to watch the lifecycle: active (no push needed, or push complete), pending / pushing (queued for the reconciler), or error (last push attempt failed; the reconciler will retry on the next tick and the pushError field will explain why).

A snapshot that fails to push is still usable on the originating node - push failure is non-fatal. Pre-existing snapshot rows from before the feature was enabled remain active; no backfill push is implied.

A snapshot captures the container image, not the runtime state around it. Specifically:

CapturedNot captured
Container writable layer (files, installed packages)Mounted volumes (they live outside the container by design - see Volumes)
Image entrypoint and default working directoryRunning processes, open sessions, in-memory state
OS user, env vars baked at image build timeSandbox-level env vars set at create time (env), unless the snapshot's entrypoint reads them at start
Resource hints (cpu, memoryMB, diskGB) when providedNetwork/firewall rules, exposed ports, public URLs

If you need to ship state alongside the image, write it under a mount target before snapshotting - the mount stays declared on the new sandbox the next time you create, and the data lives independently of any single container.

  • Golden base image per workload. Register a py-base / node-base / cuda-base snapshot once during deploy, then create({ image: 'py-base' }) everywhere. Faster than re-running apt-get install per sandbox, and centralizes the version bump.
  • User-bound fork point. Snapshot a sandbox after a setup step (clone, build, seed), then spin per-user sandboxes from that snapshot. Each user gets a fresh writable layer rooted at the same baseline.
  • Failover-ready snapshots in cluster mode. Combine SB_SNAPSHOT_PUSH_ENABLED=true with failover.policy = "recreate" on the sandbox. When the owner dies, the cluster recreates the sandbox on a surviving node from the AOCR-distributed snapshot image - without it, the recreate is rejected because the local image is unreachable.