Skip to content
Runtime

How to run PostgreSQL in a sandbox for tests and agents

Install Ubuntu's postgresql package in a microVM, start a server with initdb and postgres, load your schema, and query it with psql.

On Runtime, a seeded database can be copied while it is running, in one call. Load the schema and fixtures once, then fork the sandbox. Every copy starts with Postgres already up and the data in memory. Ten tests or ten agent attempts each get a private database, without ten restores. Every sandbox is a Firecracker microVM. A sandbox that holds an idle database costs $0.03125 an hour for 2 vCPU and 4 GiB (pricing). Ubuntu 24.04 shipped PostgreSQL 16.15 on 25 October 2026. The PostgreSQL project's own apt repository listed 18 as its current stable release.

Start a database and load a schema

Ubuntu's package creates a cluster of its own. The sample drops that cluster and creates one in /workspace, owned by the sandbox user. This way, nothing else depends on how the system starts services.

TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";const bin = "/usr/lib/postgresql/16/bin";await using sbx = await Sandbox.create({ timeoutSeconds: 1800 });const slow = { check: true, timeoutMs: 600_000 } as const;await sbx.exec("sudo apt-get update -qq && sudo apt-get install -y -qq postgresql", slow);await sbx.exec("sudo pg_dropcluster --stop 16 main", { check: true });await sbx.exec(`${bin}/initdb -D /workspace/pgdata -U postgres --auth=trust`, slow);await sbx.spawn(`${bin}/postgres -D /workspace/pgdata -k /tmp -c listen_addresses=127.0.0.1`);await sbx.exec("timeout 60 bash -c 'until pg_isready -q -h 127.0.0.1; do sleep 0.5; done'", {  check: true,  timeoutMs: 90_000,});await sbx.files.upload("./schema.sql", "/workspace/schema.sql");const psql = "psql -h 127.0.0.1 -U postgres -v ON_ERROR_STOP=1";await sbx.exec(`${psql} -f schema.sql`, { check: true });const count = await sbx.exec(`${psql} -Atc 'select count(*) from users'`, { check: true });console.log("users:", count.stdout.trim());await sbx.exec("pg_dump -h 127.0.0.1 -U postgres -Fc -f /workspace/app.dump postgres", {  check: true,});await writeFile("app.dump", await sbx.files.read("/workspace/app.dump"));
Pythonfrom withruntime import SandboxBIN = "/usr/lib/postgresql/16/bin"PSQL = "psql -h 127.0.0.1 -U postgres -v ON_ERROR_STOP=1"with Sandbox.create(timeout_seconds=1800) as sbx:    sbx.exec("sudo apt-get update -qq && sudo apt-get install -y -qq postgresql",             check=True, timeout_ms=600_000)    sbx.exec("sudo pg_dropcluster --stop 16 main", check=True)    sbx.exec(f"{BIN}/initdb -D /workspace/pgdata -U postgres --auth=trust", check=True)    sbx.spawn(f"{BIN}/postgres -D /workspace/pgdata -k /tmp -c listen_addresses=127.0.0.1")    sbx.exec("timeout 60 bash -c 'until pg_isready -q -h 127.0.0.1; do sleep 0.5; done'",             check=True, timeout_ms=90_000)    sbx.files.upload("schema.sql", "/workspace/schema.sql")    sbx.exec(f"{PSQL} -f schema.sql", check=True)    print(sbx.exec(f"{PSQL} -Atc 'select count(*) from users'", check=True).stdout)    sbx.exec("pg_dump -h 127.0.0.1 -U postgres -Fc -f /workspace/app.dump postgres", check=True)    sbx.files.download("/workspace/app.dump", "app.dump")
  • spawn keeps the server running after the call returns. A process started with exec ends when its command ends.
  • --auth=trust lets any process in the sandbox connect without a password. Only code you run there, and you through the forward below, can reach 127.0.0.1. Nothing on the internet can connect in to a sandbox.
  • -k /tmp puts the Unix socket where the sandbox user may write it.

One seeded database per test

TypeScriptimport { Sandbox } from "withruntime";declare const seeded: Sandbox; // the sandbox above, schema and fixtures loadedconst copies = await seeded.fork({ count: 10 });const results = await Promise.all(  copies.map((db, i) =>    db.exec(`psql -h 127.0.0.1 -U postgres -f tests/case-${i}.sql`, { timeoutMs: 300_000 }),  ),);await Promise.all(copies.map((db) => db.stop()));console.log(results.map((r) => r.exitCode));

A fork copies files, memory and running processes. Postgres in each copy is therefore the same server, in the middle of its work. It is not a fresh start or a restore. The source is paused while the capture runs, and it runs again when the fork returns. Forks make 1 to 100 copies per call. To start copies later, take a snapshot and create sandboxes from it.

Connect from your laptop or a GUI

Forward the port, then point psql, DBeaver, TablePlus or your app at localhost:

Pythonimport osimport psycopg  # pip install "psycopg[binary]"from withruntime import Sandboxsbx = Sandbox.connect(os.environ["SANDBOX_ID"])with sbx.forward_port(5432, local_port=15432) as forward:    with psycopg.connect(f"postgresql://postgres@127.0.0.1:{forward.local_port}/postgres") as conn:        print(conn.execute("select version()").fetchone())
Terminalruntime sandboxes port-forward <id> 15432:5432psql postgresql://postgres@127.0.0.1:15432/postgres

The forward is a WebSocket through Runtime's API. It uses your key (forward ports) and works without credit. To give a service outside Runtime a connection string instead, a paid account can open a public TCP port (networking).

Keep the data

You want Use
Come back to the same running database Pause: files, memory and processes are kept while you have credit, and the next request wakes it
Many databases from one prepared state, later A snapshot, then sandboxes.create({ snapshot })
Data that outlives any sandbox initdb onto a volume mounted at /data; run sync after writes you must keep
A copy on your own machine pg_dump, then files.read as above

A sandbox with a volume attached cannot be snapshotted. A fork takes a snapshot. So choose for each database: fast copies, or a disk that outlives the machine.

A newer PostgreSQL, or Postgres in every sandbox

The PostgreSQL project's apt repository supports Ubuntu 24.04. Its script adds the repository. The -y flag skips its prompt:

Terminalsudo apt-get install -y postgresql-commonsudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -ysudo apt-get install -y postgresql-18   # binaries in /usr/lib/postgresql/18/bin

To skip the install in every sandbox, build PostgreSQL into a custom image. Building is free, and an account without credit stores three images free.

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({  name: "postgres",  recipe: { apt: ["postgresql"], commands: ["pg_dropcluster --stop 16 main"] },});
Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="postgres",                     recipe={"apt": ["postgresql"], "commands": ["pg_dropcluster --stop 16 main"]})

A sandbox from the image starts at the initdb step of the first sample.

For Postgres next to an app and a cache, a Compose file is often simpler. See Docker Compose integration tests. For agents that need a database to try migrations against, see a coding agent sandbox.

Sources

Facts on this page were checked on 25 October 2026.

Related.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card