How to Run MongoDB in a Cloud Sandbox
Updated October 27, 2026 .md
Short answer: Run the official mongo:8.0 Docker image in a Sandboxer microVM with a named volume, --restart unless-stopped and a port bound to 127.0.0.1, then connect from your laptop through an ssh -L tunnel. On 29,353 zip code documents, an index on city cut a query from scanning every document to reading 41.
What we ran#
Everything below ran on production on October 27, 2026, in one default sandbox (4 vCPU, 8 GB): MongoDB Community 8.0.34 from the official mongo:8.0 image, mongosh 2 on the laptop, and the mongo-express:1 image for a web UI. The data is a public sample file of US zip codes served from media.mongodb.org (3.2 MB, one JSON document per line). Timings are single runs or three back-to-back runs; yours will differ.
| Step | Result |
|---|---|
sbx new until ready |
3.2 s |
Pull mongo:8.0, start it, wait for ping |
11.3 s |
mongoimport 29,353 documents (3.2 MB) |
0.4 s |
find({city: "SPRINGFIELD"}), no index |
COLLSCAN, 29,353 docs examined, 6 to 8 ms |
Build the city_1 index |
42 ms |
| Same query with the index | IXSCAN, 41 keys and 41 docs examined, 0 ms |
Same query from a laptop through ssh -L |
157 ms median round trip |
sbx stop --wait, then sbx resume |
2.2 s and 1.4 s, memory restored, data and index intact |
1. Start MongoDB with a volume and a restart policy#
Docker is preinstalled in every sandbox, so the official image is one command. Image pulls take longer than the default 30-second sbx exec timeout, so pass --timeout:
sbx new --name mongo --type default --no-env
id=sb_ywu5u688p7 # the id that sbx new printed
sbx exec $id --timeout 600 "docker run -d --name mongo --restart unless-stopped \
-p 127.0.0.1:27017:27017 -v mongodata:/data/db mongo:8.0"
sbx exec $id "docker exec mongo mongosh --quiet --eval 'db.version()'; docker ps --format '{{.Names}} {{.Image}} {{.Status}}'"8.0.34
mongo mongo:8.0 Up 6 secondsThree flags matter:
-v mongodata:/data/dbputs the database files in a named Docker volume on the sandbox disk, so they outlive the container.--restart unless-stoppedmakes Docker start MongoDB again when the sandbox boots from disk instead of restoring memory.-p 127.0.0.1:27017:27017keeps MongoDB inside the VM. The image starts without authentication, so nothing outside the VM should reach port 27017. You connect through SSH in step 4.
2. Import a public dataset with mongoimport#
The mongo image ships with mongoimport. Download the file in the sandbox and pipe it into the container:
sbx exec $id --timeout 300 "curl -sSL https://media.mongodb.org/zips.json -o zips.json && wc -l zips.json \
&& docker exec -i mongo mongoimport --db geo --collection zips < zips.json"29353 zips.json
2026-10-27T09:31:06.308+0000 connected to: mongodb://localhost/
2026-10-27T09:31:06.691+0000 29353 document(s) imported successfully. 0 document(s) failed to import.Each document is one zip code: { "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" }.
3. Run an aggregation, then index a query#
Save this as agg.js. It totals population and zip codes per state and keeps the top five:
db = db.getSiblingDB("geo");
db.zips.aggregate([
{ $group: { _id: "$state", pop: { $sum: "$pop" }, zips: { $sum: 1 } } },
{ $sort: { pop: -1 } },
{ $limit: 5 }
]).forEach(d => print(JSON.stringify(d)));Copy it into the container and run it with mongosh --file. Piping a script to mongosh on stdin also works, but it echoes REPL prompts into the output.
sbx scp ./agg.js $id:/home/user/
sbx exec $id "docker cp agg.js mongo:/tmp/ && docker exec mongo mongosh --quiet --file /tmp/agg.js"{"_id":"CA","pop":29754890,"zips":1516}
{"_id":"NY","pop":17990402,"zips":1595}
{"_id":"TX","pop":16984601,"zips":1671}
{"_id":"FL","pop":12686644,"zips":804}
{"_id":"PA","pop":11881643,"zips":1458}Now measure a lookup by city. Save as explain.js. It prints the plan and the counters from explain("executionStats"):
db = db.getSiblingDB("geo");
const s = db.zips.find({ city: "SPRINGFIELD" }).explain("executionStats");
const e = s.executionStats, w = s.queryPlanner.winningPlan;
print(`plan=${w.stage}/${w.inputStage ? w.inputStage.stage : "-"} returned=${e.nReturned} ` +
`keysExamined=${e.totalKeysExamined} docsExamined=${e.totalDocsExamined} ms=${e.executionTimeMillis}`);Without an index, MongoDB reads every document to find 41 matches:
plan=COLLSCAN/- returned=41 keysExamined=0 docsExamined=29353 ms=7
plan=COLLSCAN/- returned=41 keysExamined=0 docsExamined=29353 ms=6
plan=COLLSCAN/- returned=41 keysExamined=0 docsExamined=29353 ms=8Create the index (index.js):
db = db.getSiblingDB("geo");
const t0 = Date.now();
print(db.zips.createIndex({ city: 1 }));
print(`build ms=${Date.now() - t0}`);city_1
build ms=42Run explain.js again. The plan is now an index scan that reads only the 41 matching keys and documents:
plan=FETCH/IXSCAN returned=41 keysExamined=41 docsExamined=41 ms=0
plan=FETCH/IXSCAN returned=41 keysExamined=41 docsExamined=41 ms=0
plan=FETCH/IXSCAN returned=41 keysExamined=41 docsExamined=41 ms=0executionTimeMillis rounds to whole milliseconds, so at this size the useful number is docsExamined: 29,353 before, 41 after. That ratio is what grows with your collection.
4. Connect mongosh or Compass from your laptop#
MongoDB listens on 127.0.0.1 in the VM, so forward a local port to it over SSH. Print the CLI's SSH stanza and save it to a file. You can also paste it into ~/.ssh/config, or let sbx ssh-config --install append it.
sbx ssh-config > ~/.ssh/sbx.conf
cat ~/.ssh/sbx.conf# Sandboxer (sbx ssh-config)
Host *.sbx
User user
IdentityFile ~/.ssh/sandboxer_ed25519
IdentitiesOnly yes
ProxyCommand sbx proxy %h
UserKnownHostsFile ~/.config/sandboxer/known_hosts
StrictHostKeyChecking yes
ServerAliveInterval 30The paths in your copy are absolute; we shortened them here. Connect once with sbx ssh so the CLI authorizes its key on the sandbox, then open the tunnel. We used local port 27018 so it can't clash with a MongoDB on your laptop:
sbx ssh $id true
ssh -F ~/.ssh/sbx.conf -f -N -o ExitOnForwardFailure=yes -L 27018:127.0.0.1:27017 $id.sbx
mongosh --quiet "mongodb://127.0.0.1:27018/geo?directConnection=true" \
--eval 'print(db.version()); print(db.zips.countDocuments()); printjson(db.zips.findOne({_id: "10001"}))'8.0.34
29353
{
_id: '10001',
city: 'NEW YORK',
loc: [
-73.996705,
40.74838
],
pop: 18913,
state: 'NY'
}MongoDB Compass and any driver take the same connection string: mongodb://127.0.0.1:27018/?directConnection=true. We tested mongosh only.
To see what the network adds, we ran the indexed SPRINGFIELD query 20 times from the laptop (lat.js, run with mongosh … --file lat.js):
const t = [];
for (let i = 0; i < 20; i++) {
const t0 = Date.now();
db.zips.find({ city: "SPRINGFIELD" }).toArray();
t.push(Date.now() - t0);
}
t.sort((a, b) => a - b);
print(`median ${t[10]} ms, min ${t[0]} ms over ${t.length} queries`);median 157 ms, min 155 ms over 20 queriesThe query takes under a millisecond in the VM. The rest is the round trip between our client and the sandbox, which depends on where you are. For load tests or code that queries in a loop, run the client in the sandbox too. sbx forward $id --remote 27017 is the CLI's built-in alternative to ssh -L; we tested the ssh -L route only.
5. Optional: Mongo Express on a private URL#
Mongo Express is a web UI for browsing collections. Put it on a Docker network with MongoDB, keep its basic auth on, and give it a hosted URL:
sbx exec $id --timeout 600 "docker network create mongonet && docker network connect mongonet mongo && \
docker run -d --name mongo-express --network mongonet --restart unless-stopped -p 8081:8081 \
-e ME_CONFIG_MONGODB_URL=mongodb://mongo:27017 \
-e ME_CONFIG_BASICAUTH_USERNAME=admin -e ME_CONFIG_BASICAUTH_PASSWORD=change-me mongo-express:1"
sbx host $id 8081 --title "Mongo Express"sbx host prints a private URL like https://simple-chief-easel-8081.box.sandboxer.com?_token=…. Treat it like a password. There are two locks, and we checked each with curl:
url='https://simple-chief-easel-8081.box.sandboxer.com?_token=…' # the full URL sbx host printed
base=${url%%\?*}
curl -s -o /dev/null -w '%{http_code}\n' "$base/" # no token
curl -s -c jar -b jar -L -o /dev/null -w '%{http_code}\n' "$url" # token, no login
curl -s -c jar -b jar -L -o /dev/null -w '%{http_code}\n' -u admin:change-me "$url" # token and login401
401
200The first 401 comes from Sandboxer: no token, so the request never reaches the sandbox. The second comes from Mongo Express itself (www-authenticate: Basic realm="Authorization Required"): the token was accepted and its cookie set, but there was no password. With both, the page title is Home - Mongo Express and the geo database lists zips. Change change-me before you use this for anything real.
6. What survives a stop and resume#
We wrote one more document, flushed to disk, then stopped and resumed:
sbx exec $id "docker exec mongo mongosh --quiet geo --eval 'db.notes.insertOne({msg: \"written before stop\", at: new Date()}); printjson(db.adminCommand({fsync: 1}).ok)'"
sbx stop $id --wait
sbx resume $id
sbx exec $id "docker ps --format '{{.Names}} {{.Status}}'; docker exec mongo mongosh --quiet geo \
--eval 'print(db.zips.countDocuments()); print(db.zips.getIndexes().map(i => i.name).join(\",\")); print(db.notes.findOne().msg)'"mongo-express Up 37 seconds
mongo Up 4 minutes
29353
_id_,city_1
written before stopThe stop took 2.2 seconds and the resume 1.4 seconds. The resume reported memoryRestored: true, so MongoDB was never restarted (Up 4 minutes). The hosted Mongo Express URL answered 200 again with the same token.
Two things did not carry over:
- The SSH tunnel. The
ssh -Lprocess on the laptop exited when the sandbox stopped. Run the samessh -F … -L …line again after the resume; the new tunnel returned thenotesdocument. - Memory, sometimes. A resume boots from disk instead when no machine with the same CPU type is free, or when you move to a bigger size. Then running processes are gone and Docker's restart policy starts both containers again from the
mongodatavolume. We did not force that case in this run; the pgvector guide shows the same pattern for Postgres. Moving to a smaller size isn't supported yet; create a new sandbox and copy the data withmongodumpandmongorestore.
7. Clean up#
sbx delete $id --yesA default sandbox costs $0.036/hour while it runs and nothing while it is stopped.
FAQ#
Do I need authentication on MongoDB in a sandbox?#
The official image starts without it unless you set MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD (see the image docs). In this guide, port 27017 is bound to 127.0.0.1 and reached only through SSH, which takes your API key. If you ever host the port or bind it to all interfaces, set a root user first.
Why does mongosh print test> and geo> prompts in my output?#
You piped the script into mongosh on stdin, which runs it as an interactive session. Copy the file into the container and use mongosh --quiet --file /tmp/script.js instead, as in step 3.
Can I reach MongoDB on a hosted HTTPS URL?#
A hosted URL serves HTTP. MongoDB clients speak MongoDB's own wire protocol over TCP, so we used an SSH tunnel and did not test a hosted URL for port 27017. Mongo Express is a web app, so it works on a hosted URL, as in step 5.
Can I run a replica set for change streams or transactions?#
Change streams and multi-document transactions need a replica set. You can start mongod with --replSet and run rs.initiate(), but we did not test that here. Follow MongoDB's replica set deployment docs.
Will my data survive an auto-stop?#
Yes. Auto-stop is a normal stop: the disk and memory are saved and sbx resume brings the sandbox back. The data lives in the mongodata volume on the sandbox disk, and the restart policy covers a resume that boots from disk.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.