Most apps need a database, and standing one up well on a cluster is a project in itself: install it, secure it, give your app the right credentials, and set up a way to back it up and restore it when you need to. Burrow's Postgres addon collapses that into a couple of commands. This guide walks through giving your app a database, taking backups, and restoring one, all without leaving the plain command surface you already use.

The shape of it

Two ideas do the work here. First, you install the Postgres addon once on your cluster. Then you attach each app to it, which hands that app its own database and role and wires the connection into the app for you. Backups are their own small set of commands on top.

Everything below runs on your cluster, on nodes you already pay for. The database is yours, the backups are yours, and nothing leaves your infrastructure.

Step 1: install the addon

Install Postgres onto your cluster once:

burrow addon install postgres

This stands up a shared Postgres that lives on your cluster. "Shared" means one database server that hands out a separate database to each app you attach, not one giant database everyone dips into. Each app gets its own isolated space.

Installing an addon is one of the actions Burrow treats as worth a moment of thought, so if your agent proposes it, by default it asks you first rather than standing up infrastructure on its own. You can confirm it, or run the install yourself as above. If you want your agent to handle addon installs without asking, you can set that policy, but out of the box the agent proposes and you decide.

Step 2: attach your app

Now give a specific app a database:

burrow addon attach postgres web

This is the command that does the magic. It creates a dedicated database and a role for web, and it wires the connection details into the app as a DATABASE_URL so your code can just read it from the environment. You do not copy a connection string around, you do not manage the password by hand, and you do not hand your app broad access to a database it shares with everything else. It gets its own.

From your app's point of view, DATABASE_URL is simply present. Connect to it the way you already connect to Postgres anywhere. The next time the app deploys, or right away, the value is there.

If you ever need to pull an app off its database, there is a matching detach, but be deliberate about it: detaching destroys that app's database and its data. It is the real teardown, not a pause.

Why per app databases matter

The "each app gets its own" part is worth pausing on, because it is doing real work for you. When several apps share one big database and one set of credentials, a mistake in any of them can reach all the others: a bad migration, a runaway query, a leaked connection string, and suddenly the blast radius is everything. Giving each app its own database and its own role keeps a problem in one app contained to that app. It is the same instinct as the guardrails elsewhere in Burrow: keep the blast radius small by default, so a mistake stays local. You attach as many apps as you like to the one installed addon, and each stays walled off from the rest.

Step 3: run your migrations

A fresh database needs its schema. Because Burrow can run a one off command inside your app's own image and environment, your migration tool has the same DATABASE_URL the app does:

burrow app run web -- ./migrate up

That runs your migration command as a one off task against the attached database, then cleans itself up. No separate migration deploy, no copying credentials into a script. The command runs in the same environment your app runs in, so it connects to the same database automatically.

The same trick works for any one off task you would normally reach into a database to do. Want to check what state the schema is in before you touch it? Run your migration tool's status command the same way:

burrow app run web -- ./migrate status

Want to seed a fresh database, or run a quick maintenance script you ship inside the image? Same pattern, whatever command your image knows how to run:

burrow app run web -- ./seed

The rule of thumb is simple: if your app can do it, burrow app run can do it as a throwaway task with the same DATABASE_URL wired in. One thing to keep in order: attach before you run. The DATABASE_URL only exists once the app is attached, so a migration run before the attach has nothing to connect to. Attach first, then run.

Step 4: take a backup

A database without backups is a problem you have not had yet. Burrow makes taking one a single command:

burrow addon backup postgres web

This captures a backup of the web app's database. Under the hood it is a straightforward dump of your data, stored so you can restore from it later. Taking a backup is one of the actions Burrow can hold for your confirmation, because it touches your data directly, so you stay in the loop on it.

To see what backups exist:

burrow addon backups postgres web

That lists the backups for the app: an id for each one, when it was taken, and how big it is. Leave off the app name to see backups across all attached apps. The id is what you use to restore.

A good rhythm is to take a backup before anything risky: before a migration that changes a lot of data, before a big release, before you try something you are not sure about. It costs you one command and buys you a way back.

Building a backup habit

Backups are one of those things everyone agrees they should do and plenty of people put off until the day they wish they had not. The trick is to attach them to events you already have, so they happen without you remembering.

Two moments cover most of the risk. The first is right before a schema migration, since a migration is the single most common way to damage data in a way that is hard to undo. Take a backup, run the migration, and if the migration goes wrong you restore in one command instead of reconstructing data by hand. The second is before a significant release, the kind where a lot is changing at once. A backup there is cheap insurance against a bug that corrupts data before you notice it.

Because taking a backup is a single command, it slots naturally into these flows, and because it is one of the actions a guardrail can hold, an agent taking a backup on your behalf can be made to pause for your sign off in prod. The goal is that "did we back up first?" is never a question you have to ask after the fact.

Step 5: restore when you need to

The whole point of a backup is the restore. When you need to bring a database back to a captured state, name the backup id:

burrow addon restore postgres web --backup <id>

Take this one seriously: a restore overwrites the app's current database with the contents of that backup. That is exactly what you want when you are recovering from a bad data change, and exactly what you do not want to run by accident. So restore reads the id explicitly and does the overwrite deliberately. Grab the id from the backups list, confirm it is the one you mean, and run it.

What your agent can and cannot do here

If you operate through an agent, all of this is available to it through the same commands, under the same guardrails. Your agent can propose installing Postgres, attach an app, run migrations, and take backups. When a page feels slow, it can even help you rule the database in or out as the cause.

That last part is worth calling out. Postgres exports its own health metrics, so once you also install the metrics addon, your database is watched automatically: connection and transaction health, and slow query stats. Install the two addons in either order. After that, "is the database the bottleneck?" is a question your agent can actually answer from data instead of guessing.

What the agent cannot do is quietly destroy your data. The destructive moves here, a detach that drops a database, a restore that overwrites one, are the kind of operations Burrow is built to keep a human in the loop on. The agent proposes, you decide. That is the same promise that runs through everything Burrow does, applied to the place it matters most: your data.

A database per environment

If you run more than one environment, staging and prod, each environment gets its own attached database. That is the point: your staging app talks to a staging database, your prod app talks to a prod database, and a mistake in one cannot reach the other. You attach in each environment the same way, selecting the environment first:

burrow env use staging
burrow addon attach postgres web

burrow env use prod
burrow addon attach postgres web

Backups are per app and per environment too, so a backup you take in staging lists and restores in staging, and prod stays its own world. This is where the confirm guardrail earns its keep: you can leave staging loose and hold the data touching moves in prod for your sign off, so an agent taking a backup or, more importantly, running a restore in prod pauses for you first.

burrow guard set --env prod addon.install confirm

What can go wrong, and how it is handled

A few sharp edges are worth naming up front, because knowing them turns a surprise into a non event.

The DATABASE_URL is not there until you attach. Attaching is what creates the database and wires the connection in. If your app was already running before you attached, the value lands on the app's config and the app rolls so it picks it up. Deploy or attach, then let the app come back up, and the variable is present. Code that reads DATABASE_URL at startup will have it on the next start.

Detach is a real teardown, not a pause. burrow addon detach postgres web drops that app's database and its data. There is no soft version of it. If you only want to stop an app for a while, stop the app; do not detach its database.

Restore overwrites the current database. burrow addon restore replaces what is there now with the contents of the backup you named. That is exactly right when you are recovering, and exactly wrong by accident, which is why the id is explicit and the operation is one a guardrail can hold. Read the id off the backups list, make sure it is the one you mean, and run it deliberately.

Backups are gated on purpose. Taking a backup and running a restore both touch your data directly, so they are the kind of action Burrow can hold for confirmation. When an agent proposes one on your behalf in a locked down environment, it pauses for your sign off rather than acting on its own.

A few common questions

Can two apps share one database? The model is one database per app: each attach hands that app its own isolated database and role. That isolation is the feature, so sharing a single database between apps is deliberately not the shape here. Attach each app that needs a database and let each stay walled off.

Where do the backups live? On your own cluster, on the nodes you already pay for. Nothing is shipped off to a third party. The database is yours, the dumps are yours, and neither leaves your infrastructure.

How do I make backups regular? Because taking one is a single command, the reliable pattern is to attach it to events you already have: run burrow addon backup postgres web as the step right before a schema migration or a significant release. Tie it to the risky moment and "did we back up first?" stops being a question you ask after the fact.

Can my agent see whether the database is healthy? Yes, once the metrics addon is installed. Postgres exports its own metrics, so the collector scrapes it automatically: connection and transaction health, plus slow query statistics from pg_stat_statements. Install the two addons in either order, and "is the database the bottleneck?" becomes a question answered from data rather than a guess.

Putting it together

Here is the whole lifecycle in one view:

burrow addon install postgres          # once, on the cluster
burrow addon attach postgres web       # give the app its own database
burrow app run web -- ./migrate up     # set up the schema
burrow addon backup postgres web       # take a backup before anything risky
burrow addon backups postgres web      # see what you have
burrow addon restore postgres web --backup <id>   # come back to a known state

A real database, wired into your app, with backups and restores that are one command each and never leave your cluster. That is the unglamorous foundation most apps need, made boring in the best way. And boring is exactly the goal here. A database should not be a source of drama. It should be there, isolated per app, quietly backed up before anything risky, and restorable in one command on the rare day you need it. Get that right and the database stops being the part of your stack you worry about, which frees you to worry about the parts that are actually your product.