Runtime

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:

  • -readonly opens the database read-only.
  • -safe turns off every shell feature that reads or writes a file other than the database: ATTACH, readfile(), writefile(), load_extension(), .shell, .system, .import, .output and more.
  • timeoutMs ends a runaway query from outside; the result says timedOut: 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|500500
Pythonfrom 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.

Facts on this page were checked on 25 September 2026.