How to run SQL queries in a sandbox (SQLite and PostgreSQL)
Run the SQL against a throwaway database inside a Linux microVM: SQLite with the sqlite3 shell, or PostgreSQL installed from apt.
On Runtime SQLite is already in every sandbox, and a full PostgreSQL server is
one apt install away. Each sandbox is a Firecracker microVM running Ubuntu
24.04.5 with sqlite3 in the image and passwordless sudo, so a query a model
wrote can run against a copy of the data, never your production database. A
new sandbox took 351 ms from the create request to its first Python result at
the median, measured on 24 September 2026 (speed).
SQLite or PostgreSQL?
| Question | SQLite | PostgreSQL |
|---|---|---|
| In the default image? | Yes, the sqlite3 shell |
No: sudo apt-get install -y postgresql |
| Ubuntu 24.04 package, 25 Sep 2026 | sqlite3 3.45.1 |
postgresql 16 (postgresql-16 16.15) |
| Runs as | A command on a file | A server on port 5432 |
| Also reachable from | Python's sqlite3 module, in the image |
psql, any driver, on localhost |
| Best for | One-off queries, grading, CSV analysis | Testing SQL that production runs on Postgres |
Run a query on SQLite
Pass the SQL on stdin and name the database as an argument. With -bail
the shell stops at the first error; -json prints rows as JSON for your code to
parse (sqlite3 command-line shell).
TypeScriptimport { Sandbox } from "withruntime";const setup = `CREATE TABLE orders(id INTEGER PRIMARY KEY, customer TEXT, total REAL);INSERT INTO orders(customer, total) VALUES ('ada', 30.5), ('bo', 12), ('ada', 7.25);`;const query = "SELECT customer, SUM(total) AS spent FROM orders GROUP BY customer ORDER BY spent DESC;";await using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 300, onLeaseEnd: "stop",});await sbx.exec(["sqlite3", "-bail", "shop.db"], { stdin: setup, check: true });const rows = await sbx.exec(["sqlite3", "-bail", "-safe", "-json", "-readonly", "shop.db"], { stdin: query, timeoutMs: 30_000,});console.log(rows.exitCode, rows.stdout); // [{"customer":"ada","spent":37.75}, ...]Pythonfrom withruntime import Sandboxsetup = """CREATE TABLE orders(id INTEGER PRIMARY KEY, customer TEXT, total REAL);INSERT INTO orders(customer, total) VALUES ('ada', 30.5), ('bo', 12), ('ada', 7.25);"""query = "SELECT customer, SUM(total) AS spent FROM orders GROUP BY customer ORDER BY spent DESC;"with Sandbox.create(network={"internet": False}, timeout_seconds=300, on_lease_end="stop") as sbx: sbx.exec(["sqlite3", "-bail", "shop.db"], stdin=setup, check=True) rows = sbx.exec(["sqlite3", "-bail", "-safe", "-json", "-readonly", "shop.db"], stdin=query, timeout_ms=30_000) print(rows.exit_code, rows.stdout)Three flags make the shell safer for SQL you did not write:
-readonlyopens the database read-only.-safeturns off every shell feature that reads or writes a file other than the database:ATTACH,readfile(),writefile(),load_extension(),.shell,.system,.import,.outputand more.timeoutMsends a runaway query from outside; the result saystimedOut: true.
To query your own data, upload the database file with sbx.files.write or a
folder with sbx.files.upload, and download the changed file after.
Run PostgreSQL in the sandbox
Ubuntu's postgresql package creates a cluster named 16/main. Start it with
pg_ctlcluster, which needs no service manager, and create a role for the
sandbox user so psql connects over the local socket without a password.
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ timeoutSeconds: 1800, onLeaseEnd: "stop" });const slow = { check: true, timeoutMs: 600_000 } as const;await sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q postgresql", slow);await sbx.exec("sudo pg_ctlcluster 16 main status || sudo pg_ctlcluster 16 main start", slow);await sbx.exec( "sudo -u postgres createuser --superuser runtime && createdb -O runtime runtime", slow,);await sbx.network.set({ internet: false });await sbx.files.write( "/workspace/check.sql", "CREATE TABLE t(n int); INSERT INTO t SELECT generate_series(1, 1000); SELECT count(*), sum(n) FROM t;",);const run = await sbx.exec(["psql", "-v", "ON_ERROR_STOP=1", "-At", "-f", "check.sql"], { timeoutMs: 60_000,});console.log(run.exitCode, run.stdout); // 1000|500500Pythonfrom withruntime import Sandboxwith Sandbox.create(timeout_seconds=1800, on_lease_end="stop") as sbx: sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q postgresql", check=True, timeout_ms=600_000) sbx.exec("sudo pg_ctlcluster 16 main status || sudo pg_ctlcluster 16 main start", check=True) sbx.exec("sudo -u postgres createuser --superuser runtime && createdb -O runtime runtime", check=True) sbx.network.set(internet=False) run = sbx.exec(["psql", "-v", "ON_ERROR_STOP=1", "-At", "-c", "SELECT version();"], timeout_ms=60_000) print(run.exit_code, run.stdout)ON_ERROR_STOP=1 makes psql stop at the first failing statement and exit
non-zero, so a broken migration fails the command. The network rule refuses
connections out of the sandbox; localhost inside it keeps working.
Start every sandbox with Postgres running
An image can install PostgreSQL and say how a sandbox starts it. The create call answers once port 5432 is listening (start and ready commands):
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({ name: "postgres", recipe: { apt: ["postgresql"] }, start: { command: "sudo pg_ctlcluster 16 main start && sleep infinity", readyPort: 5432, readyTimeoutSeconds: 60, },});await using sbx = await runtime.sandboxes.create({ image: "postgres", network: { internet: false },});console.log(sbx.info.start); // { state: "ready", ... }await sbx.exec("sudo -u postgres psql -c 'SELECT 1'", { check: true });pg_ctlcluster returns once the server is up, and a start command that ends
counts as exited, so sleep infinity keeps it running.
For many test runs against the same seeded data, load it once, then fork the running sandbox: each copy starts with the server up and the data in memory, and nothing one copy writes reaches another.
What it costs
A database that sits idle between queries costs the CPU floor, a twentieth of a vCPU at $0.025 per vCPU-hour, plus memory at $0.0075 per GiB-hour. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour waiting and $0.08 an hour fully busy (pricing). New accounts get 50 free sandbox hours, no card:
Terminalnpx withruntime sandbox run --trial -- sqlite3 :memory: 'select sqlite_version();'See also a data analysis agent, agent evals and running untrusted LLM code.
Sources
Checked 25 September 2026.
- SQLite command-line shell:
-bail,-json,-readonly,-safe - Ubuntu 24.04 (noble) packages: sqlite3, postgresql, postgresql-16, postgresql-common file list (
pg_ctlcluster)
Facts on this page were checked on 25 September 2026.