dbcenter
0.4.3 on PyPI · PostgreSQL 18 · MIT

A real PostgreSQL.
One pip install.

For your Python app, notebook, tests or AI agent. No server to run, no Docker. Agents get a read-only connection that PostgreSQL itself enforces, and your data can keep a copy in a bucket you own.

Try it in Colab
Linux, macOS, Windows pgvector & PostGIS MCP server pytest fixture
import dbcenter

# starts PostgreSQL 18; the data stays in ./data
db = dbcenter.DBCenter(path="./data")
db.query("CREATE TABLE notes (body text)")
db.query("INSERT INTO notes VALUES ($1)", ["milk"])
print(db.query("SELECT * FROM notes"))

print(db.url)   # for psql, psycopg, SQLAlchemy
db.shutdown()     # stops with your program
db.query("SELECT sum(total) FROM orders",
         read_only=True)          # fine

db.query("WITH x AS (DELETE FROM orders "
         "RETURNING *) SELECT 1",
         read_only=True)
# refused by PostgreSQL itself:
# cannot execute SELECT in a read-only transaction
# pip install dbcenter - and pytest has it
def test_orders(dbcenter_db):
    dbcenter_db.query("CREATE TABLE orders (total int)")
    dbcenter_db.query("INSERT INTO orders VALUES (10)")
    n = dbcenter_db.query("SELECT count(*) AS n FROM orders")
    assert n[0]["n"] == 1

def test_sqlalchemy(postgres_url):
    engine = create_engine(postgres_url)
bucket = dbcenter.S3StorageAdapter("my-bucket", ...)

laptop = dbcenter.DBCenter(path="./laptop",
                           multi_user_sync=True)
laptop.collection("notes").insert_one({"body": "hi"})
dbcenter.SyncEngine(laptop, bucket).sync_table("notes", 10_000)

# any other machine rebuilds from the bucket
dbcenter.SyncEngine(server, bucket).pull_and_apply("notes")

What you get

The actual postgres, started by your program.

Not an emulation and not a fork. DBCenter downloads PostgreSQL once, runs it as a child process against a data directory, and stops it with your program. Every SQL feature works.

Read-only for AI agents

read_only=True runs SQL in a read-only transaction that is rolled back. PostgreSQL refuses the write, however it is phrased. Nothing parses the SQL, so nothing can slip past.

An MCP server

dbcenter-mcp gives Claude, Cursor or any MCP client its own PostgreSQL. Read-only unless you pass --allow-writes.

A pytest fixture

Installing the package gives pytest dbcenter_db: one real server per session and an empty schema for every test. No Docker, no service container.

A copy in your own bucket

Each table ships what changed to S3, Cloudflare R2, Tigris or MinIO as Parquet, and another machine rebuilds from it. Lose the machine, keep the data.

Fast analytics from the same files

With columnar=True, aggregates read that Parquet through DuckDB: 4.5x to 8.7x faster than PostgreSQL on one core. Lookups stay on PostgreSQL.

Extensions in one call

install_extension("pgvector"), PostGIS, pg_trgm or pg_duckdb. Every download is checked against a pinned SHA-256 before it is loaded.

See it in action

Three things it is for.

Click each example to expand it. Every one runs as-is after pip install dbcenter.

SQL for an agent, without handing it your dataread_only=True, enforced by PostgreSQL
import dbcenter

db = dbcenter.DBCenter(path="./data")
db.query("CREATE TABLE IF NOT EXISTS orders (id serial, total numeric)")
db.query("INSERT INTO orders (total) VALUES (10.5), (99)")

print(db.query("SELECT sum(total) AS revenue FROM orders", read_only=True))
for sql in ["DELETE FROM orders",
            "WITH gone AS (DELETE FROM orders RETURNING *) SELECT count(*) FROM gone",
            "DROP TABLE orders"]:
    try:
        db.query(sql, read_only=True)
    except Exception:
        print("refused:", sql)
db.shutdown()
The same guarantee as an MCP server: pip install "dbcenter[mcp]", then claude mcp add dbcenter -- dbcenter-mcp --path /abs/path/to/data.
Test against PostgreSQL, not something like itpytest fixtures, no Docker
import psycopg

def test_orders(dbcenter_db):
    dbcenter_db.query("CREATE TABLE orders (id serial, total numeric)")
    dbcenter_db.query("INSERT INTO orders (total) VALUES (10)")
    assert dbcenter_db.query("SELECT count(*) AS n FROM orders")[0]["n"] == 1

def test_tables_do_not_leak(dbcenter_db):
    # every test starts with an empty public schema
    assert dbcenter_db.query("SELECT count(*) AS n FROM pg_tables WHERE tablename = 'orders'")[0]["n"] == 0

def test_your_own_driver(postgres_dsn):
    with psycopg.connect(postgres_dsn) as conn:
        assert conn.execute("SELECT 1").fetchone() == (1,)
One server per test session, so PostgreSQL starts once. postgres_url gives SQLAlchemy the same database.
Your data in a bucket you ownS3, R2, Tigris or MinIO, as Parquet
import dbcenter

bucket = dbcenter.S3StorageAdapter(
    "my-bucket",
    endpoint="https://<account>.r2.cloudflarestorage.com",
    access_key="...", secret_key="...",
)

laptop = dbcenter.DBCenter(path="./laptop", multi_user_sync=True)
laptop.collection("notes").insert_one({"body": "remember the milk"})
dbcenter.SyncEngine(laptop, bucket).sync_table("notes", 10_000)
laptop.shutdown()

server = dbcenter.DBCenter(path="./server", multi_user_sync=True)
dbcenter.SyncEngine(server, bucket).pull_and_apply("notes")
print(server.query("SELECT body FROM notes"))
Only what changed since the last sync is sent. Several writers can share one bucket: the newest edit wins, and the losing version is kept rather than dropped.

Measured, not claimed

Performance that matters.

Against a plain PostgreSQL with the same limits: 1 CPU and 1 GB, the cheapest tier anyone rents. Re-run on 17 October 2026.

Whole-table aggregate
1,000,000 rows on one core
8.7x faster

columnar=True, reading the synced Parquet through DuckDB

DBCenter (columnar)44.5 ms
PostgreSQL389.1 ms
Monthly bill
A 100 GB table
4.7x cheaper

List prices, the same one-core machine in both, so only storage differs

DBCenter on Fly + R2$7.69
Supabase Pro$36.50
Everyday queries
0.6 ms vs 0.6 ms

The newest 100 of 200,000 rows: the same speed as PostgreSQL, because it is PostgreSQL.

Trade-off: cold start
7.3 s vs 1.4 s

A node that rebuilds from the bucket takes longer to come up.

Trade-off: memory
123 MB vs 41 MB

About three times plain PostgreSQL. The bucket is for durability and analytics, not for serving requests.

The method, the rig and every number →

Try it in a minute.

A Colab notebook runs SQL, agent safety, pgvector memory and a bucket sync.

Open in Colab

Architecture

Small on purpose.

Storage, planning and transactions are PostgreSQL's. DBCenter adds the lifecycle and the copy in your bucket.

  1. A real postgres binaryDownloaded once to ~/.dbcenter, then started against your data directory.
  2. Stopped with your programA small watcher process shuts PostgreSQL down cleanly, even if your program crashes.
  3. An ordinary connectiondb.query() from Python, or db.url for psql, psycopg, SQLAlchemy and any driver in any language.
  4. Parquet in your bucketOne column per field, so DuckDB can read it directly for analytics.
STARTED BY DBCENTER
PostgreSQL 18embedded
Your Python app AI agent · MCP pytest psql & drivers S3 / R2 bucket · Parquet

Alternatives

How it compares.

SQLite (+ Litestream)

Lighter and simpler. But it is not PostgreSQL: no pgvector, no PostGIS, and different SQL from the database you deploy.

testcontainers / Docker

Real PostgreSQL too, but it needs Docker on every laptop and CI runner, and it does not run in Colab.

pgserver, postgresql-wheel

The same idea in Python. Neither has been updated since 2024 and 2021, and they stop at PostgreSQL 16 and 14.

PGlite

PostgreSQL compiled to WASM, for JavaScript. A single connection, in the browser rather than in Python.

Copied