---
title: "How to Run MongoDB in a Sandbox"
description: "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."
url: "/docs/guides/run-mongodb-in-a-sandbox"
---

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

```bash pnpm
pnpm add freestyle
```

```bash bun
bun add freestyle
```

```bash npm
npm install freestyle
```

```bash yarn
yarn add freestyle
```

Set your API key before calling the API:

```bash
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`).

```ts
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`).

```ts
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](https://www.freestyle.sh/docs/vms/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:

```ts
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.
```
