Install PostgreSQL once into a snapshot, then launch VMs that boot with the database ready to serve.
Install the SDK
pnpm add freestylebun add freestylenpm install freestyleyarn add freestyle Set your API key before calling the API:
export FREESTYLE_API_KEY="your-api-key"
Build a snapshot with a running database
The base image is minimal, so install PostgreSQL explicitly with apt-get. Do this once on a
builder VM, snapshot it, then throw the builder away.
A Freestyle snapshot is a full memory and disk capture, so it preserves a running service and its in-memory state, not just the installed files. So instead of stopping at “installed”, start the server and wait until it is accepting connections before snapshotting. The snapshot then bakes in a live, ready database, and every VM you create from it boots with Postgres already serving.
The PostgreSQL package ships a systemd unit (postgresql). systemctl start is a one-shot
command — it tells systemd to launch the unit and returns immediately — so plain vm.exec is the
right tool. systemd then supervises the daemon and restarts it if it ever crashes.
import { freestyle } from "freestyle";
const { vm: builder } = await freestyle.vms.create();
await builder.exec(
"apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq postgresql",
);
// Start the database and wait until it is accepting connections.
await builder.exec("systemctl start postgresql");
let ready = false;
while (!ready) {
await new Promise((r) => setTimeout(r, 1000));
const probe = await builder.exec('su postgres -c "pg_isready" || true');
ready = probe.stdout?.includes("accepting connections") ?? false;
}
// Capture the running, ready database into the snapshot.
const { snapshotId } = await builder.snapshot();
await builder.delete();
Query the database
Create a fresh VM from the snapshot. Because the snapshot captured a running, ready server, the new VM comes up with Postgres already active and accepting connections — there is nothing to start and nothing to wait for. Just connect and query.
const { vm } = await freestyle.vms.create({
snapshotId,
idleTimeoutSeconds: null,
});
The postgres OS user owns the cluster, so run client commands as that user with su postgres.
Each query is a one-shot command, so use synchronous vm.exec:
await vm.exec(
`su postgres -c "psql -c 'CREATE TABLE fruits (id serial PRIMARY KEY, name text);'"`,
);
await vm.exec(
`su postgres -c "psql -c \\"INSERT INTO fruits (name) VALUES ('apple'), ('banana');\\""`,
);
const rows = await vm.exec(
`su postgres -c "psql -c 'SELECT id, name FROM fruits ORDER BY id;'"`,
);
console.log(rows.stdout);
// id | name
// ----+--------
// 1 | apple
// 2 | banana
// (2 rows)
Stream the Server’s Logs
vm.exec() buffers a command and only returns once it finishes, so it can’t show a long-running service’s output as it happens. To watch the logs live, open a PTY on the VM — a real terminal streamed over a WebSocket (server-side only, Node 22+) — and follow the unit’s journal. onData delivers the bytes as they arrive:
const session = await vm.pty.open({
cols: 120,
rows: 30,
onData: (bytes) => process.stdout.write(bytes), // live log lines
});
// Follow the service; new lines stream in until you detach.
session.write("journalctl -u postgresql -f\n");
// session.detach() drops your handle — the service keeps running in the VM.