Blog

The app you can pg_dump

Ask a team a plain operational question and watch the answer sprawl: what do you have to restore to bring your application back? For most stacks the honest list is long. The code lives in a container image in a registry. The data lives in a managed Postgres with its own backup schedule. The static files live in an S3 bucket with its own lifecycle rules. The configuration lives in environment variables set in a dashboard. Recovery is not restoring one thing. It is restoring four things and hoping their versions line up.

They usually line up. Until the day the image is one build ahead of the migration that was supposed to ship with it, or the assets in the bucket are last week's, and now you are doing forensics during an outage instead of running a restore.

One database, whole application

pg-web runs the web server inside Postgres, so the parts of your app that normally scatter across those four systems are rows in tables instead. pg-web push writes your routes into pgweb.routes, your templates into pgweb.templates, and your static files into pgweb.assets. Your handlers are functions in the database. Your data is, obviously, in the database. Nothing is left over.

So the tool that backs up your application is the tool that backs up Postgres:

pg_dump -Fc myapp > myapp.dump

That file is the whole thing: routes, templates, assets, handler functions, migrations, settings, and every row of your data, captured as one transaction-consistent snapshot. The code and the data in that file came out of the same instant, so they cannot disagree about what version they are. They are the same version.

What that buys you

Cloning production to a laptop stops being a project. Take a dump, restore it into a local Postgres that has the extension, and you have the running site with its data:

createdb clone
pg_restore -d clone myapp.dump
curl localhost:8080/todos/42   # the worker boots with the DB, and serves

There are no fixtures to keep current, no seed script that drifted from reality a year ago. You are debugging the actual system, not a sketch of it.

Because the framework's own bookkeeping is also just tables, you can ask the database questions you would otherwise need a separate tool to answer. Which routes are actually live?

SELECT path FROM pgweb.routes ORDER BY path;

When did the last few deploys happen, and what did each carry? pg-web writes a row to pgweb.deployments on every push:

SELECT pushed_at, from_host, file_count, migrations_applied
FROM pgweb.deployments
ORDER BY pushed_at DESC
LIMIT 5;

Two dumps taken at different times can be compared the same way, because the deployment history travels inside them. What changed between Tuesday and Friday is a query, not a dig through CI logs.

The trade-off, stated plainly

None of this makes the dump your source of truth. Git is your source of truth. Your .sql and .html files, your migrations, and their history live in a repository, get reviewed, and get pushed. The database holds the deployed copy of all of that, and a dump is a snapshot of what was deployed at one moment. It is a good snapshot: consistent, portable, restorable onto plain Postgres. It is not a replacement for version control. A dump tells you what was running on Friday. It does not tell you why, or who changed it, or what the reviewer said. That is what the repo is for.

The framing we keep coming back to: git is the history, the database is the artifact. A dump is a deploy you can hold in your hand.

Deploy and restore are the same shape

That symmetry is the part worth sitting with. A normal deploy is a pipeline: build the image, push it, run the migration, restart the app, watch the graphs. Here the deploy is a restore. Take a dump to any Postgres that has the extension, replay it, and the background worker boots with the database and starts serving. There is no separate application to start, because the application was in the file.

You can stand up a branch preview by restoring a base dump into a fresh database and pushing the branch's files into it. You can roll back to last night by restoring last night's dump, and you get last night's handlers and templates along with last night's data, not one without the other. And if you ever want to leave, the way out is pg_dump: a format older than most of the frameworks it competes with, understood by every Postgres tool ever written, and restorable onto vanilla Postgres.

There are edges worth naming. Assets live in a table with a per-file cap (currently 20 MiB), which is fine for fonts, icons, and CSS but wrong for multi-gigabyte video; large media belongs behind a CDN, and streaming very large objects out of the database is future work, not a shipped feature. Any secrets you keep in the database are in the dump too, so exclude them before handing a dump to anyone else. Those edges are real. They do not change the core property: your application is one file, and you already know how to move it.

The rest of how this fits together is on the architecture page.