Rules: keep state
Last updated 2 November 2026
Machines are not open yet. These pages describe Machines as they will open; until then, a create answers region_not_offered (422), “Machines are not offered in any region yet.”
Check these rules before you rely on what your Machine keeps: each keeps its data safe across a sleep, a wake or a crash.
The examples use the database a Machine opens as Keep data in its database and Volume shows, and a node:http server.
Keep what must last in its database or on its Volume
Every wake starts your image on fresh disks with empty memory, and only the database and the Machine’s Volume keep data durably. On the Volume, a file is durable once a flush such as fsync returns; close alone is not one (see Sleep guarantees).
db.prepare("INSERT INTO notes(id, body) VALUES (?, ?)").run(id, body);
await db.synced();writeFileSync(`/scratch/notes/${id}.json`, body); // gone at the next wakeReconnect, and read the clock again, after a wake
A wake is a new boot: every connection your program had is gone, and so is every timer it set (see What a wake starts with).
async function call(body) {
try {
return await upstream.send(body);
} catch {
upstream = await connect(); // the connection did not survive the sleep
return await upstream.send(body);
}
}const upstream = await connect(); // once, at start: fails after the next sleep
setTimeout(expireSessions, 60_000); // fires at once, or never, across a sleepAwait synced() before you answer
A reply sent before synced() resolves can announce a commit that a crash then loses (see Answer after synced()).
db.prepare("INSERT INTO orders(id, item) VALUES (?, ?)").run(order.id, order.item);
await db.synced();
res.writeHead(201).end();db.prepare("INSERT INTO orders(id, item) VALUES (?, ?)").run(order.id, order.item);
res.writeHead(201).end(); // the order is only on this Machine's diskOpen the database with the settings the Machine gives you
The platform names the database and its cache directory, and its bound on what may wait keeps the backlog small enough to send before a sleep ends (see Open it).
import { open } from "@tonbo/sqlite-ursula";
// On a Machine the platform always sets these: the database, its cache
// directory and how much may wait to be sent. It also keeps the token that
// opens the database fresh.
const db = await open(process.env.TONBO_DATABASE_URL, {
dir: process.env.TONBO_VFS_CACHE_DIR,
maxPendingBytes: Number(process.env.TONBO_VFS_MAX_PENDING_BYTES),
});import { open } from "@tonbo/sqlite-ursula";
// The default backlog can outgrow what a sleep sends in time.
const db = await open(process.env.TONBO_DATABASE_URL);Handle SIGTERM: sync and exit within the stop grace
Every sleep sends SIGTERM and stops the Machine its stop grace later, 10 seconds by default, whatever stop signal the image names (see Stopping).
process.on("SIGTERM", async () => {
server.close(); // take no new requests
await db.synced(); // every commit is durable from here
db.close();
process.exit(0);
});process.on("SIGTERM", () => {}); // stopped anyway when the grace endsFinish the work before you answer, or keep the request open while it runs
Only an open request keeps a Machine awake, so work left running after the answer stops at the next sleep (see What keeps it awake).
const result = await runJob(job);
db.prepare("INSERT INTO results(id, body) VALUES (?, ?)").run(job.id, JSON.stringify(result));
await db.synced();
res.writeHead(200).end(JSON.stringify(result));res.writeHead(202).end('{"state":"queued"}');
runJob(job); // nothing keeps the Machine awake for this