Skip to content

Database Commands

rebase db is the family of commands that reach the project’s database. Each one works on the database the project resolves, in the order Database Branching lists: --database-url, then DATABASE_URL in the shell, the branch the checkout is switched to, DATABASE_URL in .env, --docker, and otherwise the managed development database rebase dev starts. rebase db url prints which one that is.

push, generate and migrate are described in depth on Schema Generation, and every other command is in the CLI reference.

Push schema changes directly to the database (development only):

rebase db push

--database-url <url> names the database, and --docker the compose one, started if needed. generate, migrate, branch, backup and restore take both too.

It leaves alone what your collections do not describe: other tables and views, indexes you wrote, and the CHECK and EXCLUDE constraints on a collection’s table, which a collection cannot declare. See rebase db push for what it applies and what it stops before.

Generate SQL migration files from schema changes:

rebase db generate

Creates timestamped migration files in drizzle/migrations/ that can be reviewed and committed.

Run pending database migrations:

rebase db migrate

Applies all unapplied migrations to the database.

On a database that has already booted, the first migration creates tables boot already made, so the first db migrate there stops on already exists. Record that migration as applied instead, once:

rebase db migrate --baseline <version>

The version is the numeric prefix of the migration file; db generate prints the command with it filled in. Every db migrate after that is an ordinary one. See --baseline for a database that already records migrations.

rebase db backup --out ./backups # or s3://bucket/prefix, gs://bucket/prefix
rebase db backups list # list what is stored
rebase db restore ./backups/<file>.dump --yes

backup runs pg_dump; restore runs pg_restore and, being destructive, asks for a typed yes on a terminal, or takes --yes; with no terminal and no --yes it refuses and says to pass it. Since 0.25 Into a database that already holds what the backup creates, restore needs --clean. Without it, it refuses before anything is applied, the cluster roles included, and names the objects in the way and both ways round them: --clean, or --create-db --target-db <new name>. On 0.24 it applied the roles and then failed on pg_restore’s schema "rebase" already exists. Since 0.25 restore refuses the managed development database: a backup carries its rows as COPY data, which it cannot receive, and on 0.24 the restore left it hung until it was stopped. Restore into Postgres in Docker (rebase db restore <dump> --docker --clean --yes), then rebase db pull --from "$(rebase db url --docker)" --yes copies it into the managed database. Since 0.25 Relative paths, and the default ./backups, resolve against the directory you run rebase from, so the three lines above meet in one directory; an option a command does not take is refused before anything runs. On 0.24 the default is backend/backups, so list it with --out ./backups. Since 0.25 The dump and its roles file are readable by you only (0600), and against a database that is not running, backup and restore print the banner db push does. On 0.24 dumps are 0644. Scheduling, the roles file that travels with each dump, and the restore procedure are in Backups and restore.

Copy another database into the local development one:

rebase db pull --from postgres://… [--anonymize]

--anonymize replaces personal fields on the way in, so a production copy can be worked on locally without carrying real customer data onto a laptop. It matches columns by name and is best-effort. Since 0.25 A column under a unique index gets a distinct value per row, and the pass is one transaction: if it fails, the tables it would have rewritten are emptied and named, and the command exits 1. On 0.24 it failed on token_hash once users had signed in twice, leaving real data behind.

pg_dump strips privileges, so the copy would arrive with the source’s RLS policies and none of the grants behind them — every read as rebase_user failing with permission denied. The pull re-provisions the app role afterwards, using the same routine boot and rebase db push use, so Rebase’s internal tables stay revoked as they should be.

The target is always this project’s local development database and cannot be chosen: --database-url is refused rather than accepted, so there is no way to spell “pull into production”. --from is the only direction.

Since 0.25 A pull replaces the local database’s contents: it empties each schema it copies first, so a table that exists only locally does not survive it. Into the managed development database the rows travel as INSERT statements, which it takes where COPY would stop it. On 0.24 a pull into the managed database failed at the first table and left it answering nothing until it was killed.

Print the connection string this project is using, and nothing else, so it pipes:

rebase db url
psql "$(rebase db url)"

The managed development database is the case that needs this: .env leaves DATABASE_URL commented out on purpose, and the port is derived from the project path, so nothing on disk names it. Since 0.25 It keeps that port across a stop and a start, unless something else has taken it; on 0.24 every start picked a new one. When you have set a DATABASE_URL of your own, that is what this prints — the resolution order is the same one every other command follows. It starts the managed database if it is not already running.

Since 0.25 After rebase dev --docker, db url and the other commands use that Docker database (and say so on stderr) until a rebase dev runs without the flag. The choice is kept in .rebase/dev-database.json and ranks where --docker does, below every connection string. A --docker you type that a connection string outranks starts no container: one line on stderr says it was ignored, names what won, and says how to reach Docker instead. A rebase dev --docker outranked that way keeps the remembered choice as it was, so once the connection string is gone the commands follow Docker again. On 0.24 every command needs its own --docker, and an outranked one still starts the container.

Since 0.25 rebase init gives each project its own compose port, from 5433–5632 by the project’s path, and --docker checks that what answers on it is this project’s db container. When another container or a Postgres outside Docker holds the port, it stops and names it rather than logging in to someone else’s database. To move the project, change the host port of the db service in docker-compose.yml, and DATABASE_URL if .env sets one. On 0.24 every scaffold took the first free port from 5432, so projects made side by side could share one.

For the managed development database only:

rebase db stop # stop it; the data is kept
rebase db reset # delete it and start over

It belongs to the project, not to the command that started it, so stopping rebase dev leaves it running for the other commands. After 30 minutes with nothing connected it stops itself, and the next command that needs it starts it again. Since 0.25 rebase dev says so as it stops, with how to stop the database now; on 0.24 it said nothing.

Since 0.25 db stop under a running rebase dev says that its backend cannot reach the database until it is back. The next command that needs it, rebase db url for one, starts it on the same port, and the backend reconnects without a restart. On 0.24 it came back on another port, and the backend answered 503 until rebase dev was restarted.

rebase db branch create <name>
rebase db branch list
rebase db branch info <name>
rebase db branch switch <name> # work on it; every later command follows
rebase db branch switch # say which branch you are on
rebase db branch switch --off # back to the main database
rebase db branch delete <name>
rebase db branch prune [--older-than 14d] [--include-dev-diff]

PostgreSQL will not copy or drop a database anything else is connected to, and the usual “anything else” is your own rebase dev. create and delete name what is holding the database open; --force disconnects those sessions first.

Every branch is a full copy on disk, so they need clearing out. prune removes three things: an entry whose database was dropped outside Rebase, a branch database whose entry was never written, and — only with --older-than — branches past an age you name. It asks before removing anything unless you pass --yes. Since 0.25 With no terminal to ask on, such as in CI, it removes nothing, says to pass --yes, and exits 1; on 0.24 it says “Nothing was changed.” and exits 0. list and info only read: a database no branch was made on has none, and they create no rebase schema there. On 0.24 they create the registry.

switch records the branch in .rebase/branch.json and never edits .env. It takes precedence over DATABASE_URL in .env and loses to --database-url or a DATABASE_URL in the shell, so a flag on the command line always outranks a switch made earlier. delete refuses the branch you are on, since every command in the checkout, the delete included, is pointed at it: switch --off first, then delete it. The whole workflow is on Database Branching.