kosa8 docs

Guides / JavaScript & TypeScript / Quickstart

Quickstart

Five steps, one file. You will connect to the daemon, boot a microVM with no network, run a command in it, keep a container running and exec into it, then save the running machine, break it, and put it back.

You need kosa8 running and @kosa8/sdk installed; see Install and prerequisites. Make a file called quickstart.mjs and add each step's code to it in turn. The output under each step is what that step printed when the finished file was run once, start to finish.

Connect

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

// 1. Connect. With no options the client uses $HOME/.kosa8/run/kosa8.sock.
const kosa8 = new Kosa8Client();
console.log('kosa8', await kosa8.version(), 'at', kosa8.transport.target);
kosa8 0.9.10 at unix:///tmp/k8d/.kosa8/run/kosa8.sock

new Kosa8Client() with no options talks to the daemon's socket in your home folder. Nothing is sent until the first call. Connecting covers other sockets and remote workers.

Create a sandbox

// 2. Create a sandbox: a microVM with its own kernel and no network at all.
const { sandbox, boot_ms } = await kosa8.createSandbox({ name: 'demo', egress: 'deny' });
console.log(`sandbox ${sandbox.name}: ${sandbox.state}, egress ${sandbox.egress}, booted in ${boot_ms}ms`);
sandbox demo: running, egress deny, booted in 1155ms

A sandbox is a microVM with its own kernel, its own container engine and its own network. egress: 'deny' means nothing inside can reach the network at all, enforced on your computer where the sandbox cannot touch it. Leave name out and the daemon picks one. All the options are on Sandboxes.

Run a command

// 3. Pull an image into the sandbox (fetched on the host), then run a command to completion.
await kosa8.pullIntoSandbox('demo', 'alpine:latest');
const r = await kosa8.run('demo', 'alpine:latest', ['sh', '-c', 'uname -r; echo oops >&2; exit 3']);
console.log('exit', r.exitCode, '| stdout', JSON.stringify(r.stdout), '| stderr', JSON.stringify(r.stderr));
exit 3 | stdout "6.18.35\n" | stderr "oops\n"

A sandbox's engine does not pull images on its own, so pull first. The image is fetched on the host, which is why this works even though the sandbox has no network. run starts a fresh container from the image and waits for it to finish. A non-zero exit code is a result, not an error: the promise resolves with exitCode: 3. The kernel version is the sandbox's own, not your Mac's.

Keep a container running and exec into it

// 4. Keep a container running and exec into it. Files written here stay until the sandbox goes.
const cid = await kosa8.startContainer('demo', 'alpine:latest', ['sh', '-c', 'while :; do sleep 3600; done']);
const w = await kosa8.exec('demo', cid, ['sh', '-c', 'echo v1 > /note; cat /note']);
console.log('container', cid.slice(0, 12), '| exec exit', w.exitCode, '| /note =', w.stdout.trim());
container k2-2cf6b3b3 | exec exit 0 | /note = v1

startContainer starts a detached container and returns its id. exec runs a command in it. Unlike run, which starts from the image every time, files written in this container are still there for the next exec.

Checkpoint, break something, rewind

// 5. Checkpoint, break something, rewind. The sandbox comes back under the same name, still running.
const cp = await kosa8.checkpoint('demo', { label: 'before the risky bit' });
console.log('checkpoint', cp.checkpoint.id, `(${cp.checkpoint.label}) in ${cp.snapshot.snapshot_ms}ms`);
await kosa8.exec('demo', cid, ['rm', '/note']);
console.log('after rm:', (await kosa8.exec('demo', cid, ['cat', '/note'])).stderr.trim());
const rw = await kosa8.rewind('demo');
console.log('rewound to', rw.checkpoint.id, `in ${rw.restore_ms}ms`);
console.log('after rewind: /note =', (await kosa8.exec('demo', cid, ['cat', '/note'])).stdout.trim());

// Clean up: deletes the sandbox, its checkpoints and their snapshots.
console.log(await kosa8.deleteSandbox('demo'));
checkpoint demo-ckpt-1 (before the risky bit) in 656ms
after rm: cat: can't open '/note': No such file or directory
rewound to demo-ckpt-1 in 526ms
after rewind: /note = v1
{ ok: true, checkpoints_dropped: 1 }

A checkpoint saves the whole running machine: memory, processes and disks. A rewind puts it back under the same name, with the same container still running, so the same container id still works. deleteSandbox also deletes the sandbox's checkpoints, as kosa8 sandbox rm does. See Checkpoints and rewind.

The whole file

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

// 1. Connect. With no options the client uses $HOME/.kosa8/run/kosa8.sock.
const kosa8 = new Kosa8Client();
console.log('kosa8', await kosa8.version(), 'at', kosa8.transport.target);

// 2. Create a sandbox: a microVM with its own kernel and no network at all.
const { sandbox, boot_ms } = await kosa8.createSandbox({ name: 'demo', egress: 'deny' });
console.log(`sandbox ${sandbox.name}: ${sandbox.state}, egress ${sandbox.egress}, booted in ${boot_ms}ms`);

// 3. Pull an image into the sandbox (fetched on the host), then run a command to completion.
await kosa8.pullIntoSandbox('demo', 'alpine:latest');
const r = await kosa8.run('demo', 'alpine:latest', ['sh', '-c', 'uname -r; echo oops >&2; exit 3']);
console.log('exit', r.exitCode, '| stdout', JSON.stringify(r.stdout), '| stderr', JSON.stringify(r.stderr));

// 4. Keep a container running and exec into it. Files written here stay until the sandbox goes.
const cid = await kosa8.startContainer('demo', 'alpine:latest', ['sh', '-c', 'while :; do sleep 3600; done']);
const w = await kosa8.exec('demo', cid, ['sh', '-c', 'echo v1 > /note; cat /note']);
console.log('container', cid.slice(0, 12), '| exec exit', w.exitCode, '| /note =', w.stdout.trim());

// 5. Checkpoint, break something, rewind. The sandbox comes back under the same name, still running.
const cp = await kosa8.checkpoint('demo', { label: 'before the risky bit' });
console.log('checkpoint', cp.checkpoint.id, `(${cp.checkpoint.label}) in ${cp.snapshot.snapshot_ms}ms`);
await kosa8.exec('demo', cid, ['rm', '/note']);
console.log('after rm:', (await kosa8.exec('demo', cid, ['cat', '/note'])).stderr.trim());
const rw = await kosa8.rewind('demo');
console.log('rewound to', rw.checkpoint.id, `in ${rw.restore_ms}ms`);
console.log('after rewind: /note =', (await kosa8.exec('demo', cid, ['cat', '/note'])).stdout.trim());

// Clean up: deletes the sandbox, its checkpoints and their snapshots.
console.log(await kosa8.deleteSandbox('demo'));
$ node quickstart.mjs
kosa8 0.9.10 at unix:///tmp/k8d/.kosa8/run/kosa8.sock
sandbox demo: running, egress deny, booted in 1155ms
exit 3 | stdout "6.18.35\n" | stderr "oops\n"
container k2-2cf6b3b3 | exec exit 0 | /note = v1
checkpoint demo-ckpt-1 (before the risky bit) in 656ms
after rm: cat: can't open '/note': No such file or directory
rewound to demo-ckpt-1 in 526ms
after rewind: /note = v1
{ ok: true, checkpoints_dropped: 1 }

The same thing from the command line

The SDK and the CLI drive the same daemon, so you can look at what your program did, or finish it by hand:

$ kosa8 sandbox ls
$ kosa8 checkpoint ls demo
$ kosa8 audit ls -n 10    # your program's calls, recorded as actor "tool"
If step 5 fails with "permission denied"

On a Mac, restoring a saved machine needs the screen to be unlocked. The checkpoint succeeds, and the rewind fails with restore machine state: … “permission denied” (from kosa8 0.9.36: macOS blocks restoring a VM while the screen is locked; unlock and retry). Unlock the Mac and run it again. By then the rewind has already removed the sandbox, and the checkpoint's snapshot is intact: the error message ends with the restoreSnapshot(…) call that brings it back. See Troubleshooting.

Next

Sandboxes: every create option, and the rest of a sandbox's life.