kosa8 docs

Guides / JavaScript & TypeScript / Snapshots

Snapshots

A snapshot is a saved running machine with a name you choose: its memory, its processes and its disks. Restoring one boots a new sandbox that resumes where the snapshot was taken. Checkpoints, forks and the AI SDK adapter's stop and resume are all built on snapshots.

Take one

import { Kosa8Client } from '@kosa8/sdk';

const kosa8 = new Kosa8Client();

// Something worth saving: a running container with state in memory and on disk.
const cid = await kosa8.startContainer('runner', 'alpine:latest',
  ['sh', '-c', 'echo "started $(date +%s)" > /boot.txt; i=0; while :; do i=$((i+1)); echo $i > /counter; sleep 1; done']);
await new Promise((r) => setTimeout(r, 3000));

const s = await kosa8.snapshot('runner', { id: 'runner-before-upgrade' });
console.log(`saved ${s.snapshot.id} in ${s.snapshot_ms}ms,`, (s.snapshot.size_bytes / 2 ** 20).toFixed(0), 'MiB of machine state');
$ node snap1.mjs
saved runner-before-upgrade in 598ms, 80 MiB of machine state

The sandbox pauses for the save and carries on afterwards. The result is { snapshot, snapshot_ms, encrypted_by_policy?, encrypt_ms?, encryption? }, and snapshot.size_bytes is the saved memory state. The disks are copy-on-write clones, so they cost little until either side writes. Leave id out and the daemon names it <sandbox>-snap-<nanoseconds>. A taken id is refused with a 409.

Restore it, as often as you like

import { Kosa8Client } from '@kosa8/sdk';

const kosa8 = new Kosa8Client();
await kosa8.createSandbox({ name: 'worker', egress: 'deny', memMb: 512 });
await kosa8.pullIntoSandbox('worker', 'alpine:latest');
const cid = await kosa8.startContainer('worker', 'alpine:latest',
  ['sh', '-c', 'i=0; while :; do i=$((i+1)); echo $i > /tick; sleep 1; done']);
await new Promise((r) => setTimeout(r, 3000));

const { snapshot } = await kosa8.snapshot('worker', { id: 'worker-golden' });
console.log('saved', snapshot.id, '| tick was', (await kosa8.exec('worker', cid, ['cat', '/tick'])).stdout.trim());

// Restore as a new sandbox beside the original. Its processes resume where they were.
const r = await kosa8.restoreSnapshot('worker-golden', { name: 'worker-copy' });
console.log(`restored ${r.sandbox.id} in ${r.restore_ms}ms, state ${r.sandbox.state}`);
console.log('copy tick:', (await kosa8.exec('worker-copy', cid, ['cat', '/tick'])).stdout.trim());

// A snapshot can be restored as often as you like; delete it when done.
await kosa8.deleteSandbox('worker-copy');
await kosa8.deleteSandbox('worker');
console.log(await kosa8.deleteSnapshot('worker-golden'));
$ node restore.mjs
saved worker-golden | tick was 4
restored worker-copy in 482ms, state running
copy tick: 4
{ encrypted: false, ok: true, removed: 'worker-golden' }

The restored sandbox's counter reads 4, the value it had when the snapshot was taken: the process resumed, it did not start again. The container id from the original works in the copy. restoreSnapshot takes:

OptionMeaning
nameName of the new sandbox. Must not be in use.
labelRaise the restored sandbox's classification label.
allowTamperedRestore a sealed snapshot whose seal does not verify. The restore is recorded in the audit log as such. Without it, a broken seal refuses the restore. Not exercised for this page.

A snapshot keeps the egress rule of the sandbox it was taken from, so the restored copy has the same network. A brokered sandbox is the exception: its secrets were never saved, so it comes back with no broker and a closed network.

List and delete

import { Kosa8Client, Kosa8ConflictError } from '@kosa8/sdk';

const kosa8 = new Kosa8Client();
for (const s of await kosa8.listSnapshots()) {
  console.log(s.id, '| sandbox', s.sandbox, '|', s.cpus, 'cpus', s.mem_mb, 'MiB |', (s.size_bytes / 2 ** 20).toFixed(0), 'MiB state | egress', s.egress);
}
// Ids are unique: taking a second snapshot under a used id is refused.
try {
  await kosa8.snapshot('runner', { id: 'runner-before-upgrade' });
} catch (err) {
  console.log(err.constructor.name, err.status, '-', err.message);
}
console.log(await kosa8.deleteSnapshot('runner-before-upgrade'));
$ node snap2.mjs
runner-before-upgrade | sandbox runner | 2 cpus 1024 MiB | 80 MiB state | egress deny
Kosa8ConflictError 409 - snapshot already exists: runner-before-upgrade (choose another id, or delete the existing snapshot first)
{ encrypted: false, ok: true, removed: 'runner-before-upgrade' }

deleteSnapshot(id, { wipe?, force? }) takes the same wipe as deleteSandbox, overwriting the files first. force is needed to delete a sealed snapshot, which is otherwise refused with a 409 that says so.

Encrypt

snapshot(id, { encrypt: true }) encrypts the snapshot's memory and disks at rest, with a key kept on this machine, and then overwrites the plaintext. An org policy can require it for every snapshot, in which case the result reports encrypted_by_policy. Deleting an encrypted snapshot destroys its data key (crypto-shredding), so the ciphertext left on the SSD is useless.

const e = await kosa8.snapshot('runner', { id: 'runner-encrypted', encrypt: true });
console.log(`saved ${e.snapshot.id} encrypted in ${e.snapshot_ms}ms + ${e.encrypt_ms}ms:`, e.encryption);

On the test machine this call failed, with about 3.6 GB free on the disk:

Kosa8ApiError: snapshot runner-encrypted was taken but not encrypted: encrypted, but the plaintext of disk.img could not be wiped; run `kosa8 snapshot encrypt runner-encrypted` again (encrypt it with `kosa8 snapshot encrypt runner-encrypted`, or delete it with `kosa8 snapshot rm --wipe runner-encrypted`)

Encryption writes an encrypted copy of each disk, 1.5 GiB for the guest disk alone, and then overwrites the plaintext. On a copy-on-write filesystem such as APFS the overwrite is written to new blocks, so the operation needs free space of more than twice the guest disk while it runs. The disk had dropped to under 200 MB by the time it gave up. The snapshot was left unencrypted and the error said so, with the two ways out. Deleting it reported the key destroyed:

$ kosa8 snapshot rm runner-encrypted --force
snapshot runner-encrypted removed
  data key destroyed (crypto-shred, KEK 33ff32188e87d8ca)

untested A successful encrypt: true was not run for this page, for lack of disk space.

Snapshots and checkpoints

Snapshot

You name it. It lives until you delete it, independent of the sandbox. Restore it under a new name, as many times as you like: a golden image of a running machine.

Checkpoint

A snapshot with an automatic id and a label, on a per-sandbox stack of at most ten. rewind restores it under the same name. Deleted with the sandbox.

Next

Approvals and policy: when an org policy wants a person to sign off first.