# Databases

Managed Postgres 17 next to your apps, backed up continuously, with point-in-time restore.

## Create and attach

In the dashboard, open **Databases**, name the database and pick a size, or run `deplane db create main`. It is ready in about a minute. Then attach it to an app (`deplane db attach main --app shop`): the app gets a `DATABASE_URL` variable and rolls out a new release with it, without downtime.

```
postgres://app:…@pg--main:5432/app
```

For a cache or a job queue, see [Redis](redis.md).

Use it like any Postgres URL, for example `new Pool({ connectionString: process.env.DATABASE_URL })` in Node or `psycopg.connect(os.environ["DATABASE_URL"])` in Python. The **Notes + Postgres** template does all of this in one click.

## How it runs

- Each database runs in its own microVM, like your apps, from Deplane's pinned Postgres 17 image.
- Its data lives on a 10 GB disk that only it uses; it cannot be filled by, or fill, anything else.
- Only the apps attached to it can connect: not your team's other apps, not pull request previews, nothing on the internet and no other team. Attaching an app opens the way within seconds. Detaching it (or switching it to another database) closes the way 10 minutes later, so its running instances keep working while the release without this database rolls out; destroying the app closes it at once.
- The password is generated by Deplane, stored encrypted, and given only to attached apps inside `DATABASE_URL`, which is write-only.
- Restarts keep the data. Deleting a database deletes its data; a disk is erased before another database uses it.

## Limits

Up to 5 databases per team. A database counts against your plan as one instance of its size (small: ½ CPU and 512 MB; medium: 1 CPU and 1 GB; large: 2 CPU and 2 GB). An app uses one database at a time as `DATABASE_URL`. Backups are kept for your plan's backup window: 7 days on the Beta plan.

## Backups and restore

Every database is backed up continuously: Deplane keeps its change log as it happens and takes a full copy every night, encrypted with a key of that database alone. You can restore it to any second in the last 7 days (your plan's backup window), up to about a minute ago.

Where the backups are: on Deplane's control server, a different server from the database's, and a second copy on a separate backup server, pulled every 15 minutes. The backup server takes the copies itself: no other Deplane server holds a key that can change or delete them. Both are in the same region as your database (`fra`, Germany), still encrypted per database. If you need an export you hold yourself, run `pg_dump` from a worker or cron job.

The backup status shows on the database:

- **Protected**: backed up to less than 5 minutes ago.
- **Behind**: the newest restorable moment is 5 or more minutes old (the database was down, or backups are catching up). Deplane is alerted.
- **Starting**: the first full copy is still being taken, a few minutes after the database is created.
- **Off**: backups are not running for this database (the database is not running, or the platform's backups are off).

Times in the dashboard and in error messages are in UTC.

### Restore

A restore never changes the original: it creates a new database at the moment you pick, which you check and then switch your apps to.

- Dashboard: open the database, **Backups**, **Restore...**, pick **The latest moment** or a time, and a name.
- CLI: `deplane db restore main --at "2026-10-15 16:31"` (your local time), `--at -10m`, or `--at latest`. It prints the new database's name when it is ready, usually in a few minutes.
- Then move your apps to it: **Switch apps to this database** in the dashboard, or `deplane db switch main-restored-1431 --from main`. Each app gets a new release with the new `DATABASE_URL`, without downtime. When you switch away from the database a [template](templates.md) deploy made (or a restored copy of it), the app also gets its own database settings (such as `DB_HOST` and `DB_PASSWORD`) pointed at the new database in the same release, its generated keys stay as they are, and **Delete all of it** then deletes the new database if it is a restored copy only that app uses. It never deletes an older database or one another app uses. Delete the original when you no longer need it.

Restoring, switching and deleting a database each need their own fresh confirmation with your passkey (from the last 5 minutes, used up by the action): the dashboard asks for a tap, and the CLI shows a link and a code to approve in your browser. A stolen CLI login alone cannot do them, and CI tokens and AI tools cannot do them at all.

The restored database counts toward your plan like any other database, and needs a free place in the 5-database limit. When many restores run on the platform at once, a new one waits a few minutes for its turn to download the full copy. If every database disk on the platform is in use, creating or restoring a database is refused at once with [database_capacity](errors.md#database_capacity); try again later.

### Deleted databases

A deleted database keeps its backups for your plan's backup window. Restore it from **Recently deleted** on the Databases page, or with `deplane db restore main --deleted --at latest`. It comes back as a new database (`--to NAME`, or `main-restored-HHMM`). After the window, the backups and their key are deleted for good.

## Preview databases

Pull request previews can get their own empty databases: turn on "Give each preview its own empty database" in the app's GitHub settings (see [GitHub](github.md#pull-request-previews)). They are not on the Databases page, do not count toward your plan or the 5 databases a team can have, are capped at 3 per team, are never backed up, and are deleted for good with their preview.
