Freestyle Docs

Freestyle / Guides

How to Run MongoDB in a Sandbox

Bake a running MongoDB server into a VM snapshot, then run real document insert and find queries inside an isolated sandbox with no startup wait.

This guide builds a reusable snapshot with MongoDB already running and ready, then runs real mongosh queries inside a fresh VM — inserting a document and reading it back — without any boot or readiness wait at runtime.

Install the SDK

pnpm add freestyle
bun add freestyle
npm install freestyle
yarn add freestyle

Set your API key before calling the API:

export FREESTYLE_API_KEY="your-api-key"

Build a Snapshot with a Running MongoDB Database

Create a VM from the minimal base image and install MongoDB with apt-get. The base image is Debian 13 (trixie) and ships nothing extra, so install the prerequisites first, add MongoDB’s official apt repository (its signing key plus a source list), then install the mongodb-org metapackage. MongoDB does not yet publish its server for trixie, so point the source list at the bookworm suite — those packages install cleanly on trixie.

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 start mongod on the builder and wait until it accepts connections before snapshotting. The mongodb-org package ships a systemd unit named mongod; systemctl start mongod launches it under systemd’s supervision (its own mongodb user, reading /etc/mongod.conf, default bind 127.0.0.1:27017). That command is synchronous and returns the moment systemd has launched the unit, so calling it from vm.exec() is fine. Then poll mongosh until the server answers, snapshot the live machine, and delete the builder. The exec shell is non-login with no HOME and a bare PATH, so call binaries by absolute path (/usr/bin/mongosh).

import { freestyle } from "freestyle";

const { vm: builder } = await freestyle.vms.create();

// Prerequisites for fetching the signing key and the repo.
await builder.exec(
  "apt-get update -qq && DEBIAN_FRONTEND=noninteractive " +
    "apt-get install -y -qq gnupg curl ca-certificates",
);

// Add MongoDB's signing key and apt source (bookworm suite works on trixie).
await builder.exec(
  "curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | " +
    "gpg --dearmor -o /usr/share/keyrings/mongodb-server-8.0.gpg",
);
await builder.exec(
  'bash -c \'echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] ' +
    'http://repo.mongodb.org/apt/debian bookworm/mongodb-org/8.0 main" ' +
    "> /etc/apt/sources.list.d/mongodb-org-8.0.list'",
);

// Install the server and the mongosh shell.
const install = await builder.exec(
  "apt-get update -qq && DEBIAN_FRONTEND=noninteractive " +
    "apt-get install -y -qq mongodb-org",
);
console.log(install.statusCode); // 0
console.log((await builder.exec("/usr/bin/mongod --version")).stdout?.split("\n")[0]); // db version v8.0.23

// Start mongod under systemd and wait until it accepts connections.
await builder.exec("systemctl start mongod");

const ping = "/usr/bin/mongosh --quiet --eval 'db.runCommand({ ping: 1 }).ok'";
let ready = false;
for (let i = 0; i < 30 && !ready; i++) {
  await new Promise((r) => setTimeout(r, 1000));
  const res = await builder.exec(ping);
  ready = res.statusCode === 0 && res.stdout?.trim() === "1";
}
console.log(ready); // true

// Snapshot the live machine — the running, ready server is captured with it.
const { snapshotId } = await builder.snapshot();
await builder.delete();

Query the Running Database

Create a VM from the snapshot. Because the snapshot captured a live, ready mongod, the restored VM comes up with the server already active and accepting connections — there is no systemctl start and no readiness wait. Run one-shot mongosh queries from separate vm.exec() calls; the exec shell is non-login with no HOME and a bare PATH, so call binaries by absolute path (/usr/bin/mongosh).

const { vm } = await freestyle.vms.create({ snapshotId });

// The server is already running from the snapshot — query it immediately.
const mongosh = "/usr/bin/mongosh --quiet --host 127.0.0.1 --port 27017 --eval";

// Insert a document, then read it back.
const insert = await vm.exec(
  `${mongosh} 'db.users.insertOne({ name: "Ada", role: "admin" }).acknowledged'`,
);
console.log(insert.stdout?.trim()); // true

const find = await vm.exec(`${mongosh} 'db.users.findOne({ name: "Ada" }).role'`);
console.log(find.stdout?.trim()); // admin

Each exec returns { stdout, stderr, statusCode }, where statusCode of 0 means success. systemd keeps the unit running between calls, so every query hits the same instance and sees the document the previous call inserted.

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 mongod -f\n");

// session.detach() drops your handle — the service keeps running in the VM.
esc