Stop Fighting Your Database When Switching Git Branches
Database-per-branch workflow in Ruby on Rails using the BranchDb gem
Here's something that happens to every Rails developer eventually.
You're working on a feature branch. You add a migration, run it, everything works. Then your teammate asks you to review their PR. You switch branches. Suddenly your schema.rb shows changes you didn't make. Changes from your feature branch are now bleeding into their branch.
You know you shouldn't commit it. But now you're in a weird state. Your database has columns that don't exist in this branch's code. Things break in confusing ways.
Most developers just deal with it. They run db:migrate, hope for the best, maybe manually roll back migrations. Some teams have elaborate wiki pages explaining the "correct" way to switch branches. None of it works reliably.
The actual problem is simple: one database, many branches. That's a fundamental mismatch.
The Obvious Solution Nobody Uses
What if each branch had its own database?
Switch branches, restart your server, you're using a completely isolated database. No schema bleed. No migration conflicts. No weird states.
This isn't a new idea. Developers have been manually creating separate databases for years. But it's tedious. You have to remember to create the database, clone the data, keep track of which branch uses which database. So people don't do it.
The friction kills the solution.
Feature | The "Old Way" (Single Shared DB) | The "BranchDb Way" (Isolated DBs) |
Migration State | Bleeds between branches | Fully isolated per branch |
Schema.rb Integrity | Shows ghost changes from other branches | Always matches your current code |
Destructive Testing | Risky; affects all work | Safe; stays within one branch |
Context Switching | High mental overhead | Zero effort; "just works" |
Disk Usage | Minimal (one DB) | Higher (clones for each branch) |
Tooling Required | Manual rollbacks & wiki pages | Automated Ruby gem |
Making It Automatic
We built BranchDb at MilkStraw AI to remove that friction entirely.
Add it to your Gemfile. Run the generator. Update your database.yml with one line of ERB. That's the setup.
The rescue guard matters. BranchDb only loads in development/test, so production needs a fallback. Without it, your app won't boot in production.
Now when you run rails db:prepare on a new branch, it automatically clones from your parent branch. Not from main (unless you branched from main). From wherever you actually branched from. This cloning only happens for development databases. Test databases use standard Rails schema loading.
This matters more than you'd think.
Say you're working on feature-auth. You've added users, set up test data, run several migrations. Now you need to branch off to fix a bug in that feature. You create feature-auth-hotfix.
With BranchDb, your new branch gets a clone of feature-auth's database. All your test data, all your migrations, ready to go. You're not starting from a bare main branch and re-running everything.
How It Actually Works
The core is simple. BranchDb hooks into Rails' db:prepare task. When that task runs, it checks:
Does this branch's database exist?
Does it have a
schema_migrationstable?
If no to either, it looks for a source to clone from. First it checks git reflog to find your parent branch. Reflog analysis isn't perfect. Shallow clones in CI or complex rebase histories can break detection. When that happens, or when the detected parent's database doesn't exist, it falls back to main. You can also force a specific parent with BRANCH_DB_PARENT=some-branch rails db:prepare.
Then it uses PostgreSQL's native tools pg_dump and psql (pg_dump | psql). Fast, reliable, streaming the output directly so it doesn't consume RAM. Because it clones the actual database (not your schema file), it captures everything: tables, indexes, custom types, extensions. Works the same whether you use schema.rb or structure.sql.
If you're on main, or if no source database exists, it just defers to Rails' normal behavior. Load schema, run migrations, seed. Nothing magical.
The whole thing is under 400 lines of Ruby.
The Cleanup Problem
Branches pile up. So do databases.
After a few weeks you might have twenty branch databases sitting in PostgreSQL. Most for branches you've already merged and deleted.
BranchDb gives you three commands:
rails db:branch:list lists all branch databases.
rails db:branch:prune removes databases for git branches that no longer exist. If you deleted the branch, it deletes the database.
rails db:branch:purge is more aggressive. It removes everything except your current branch and main.
Both prune and purge ask for confirmation. Both skip databases with active connections. Both protect your current work.
What This Changes
The shift is subtle but significant.
Before: switching branches is a context-loading exercise. You're thinking about database state, migration order, whether you need to reset anything.
After: switching branches is just switching branches. The database follows automatically.
You stop treating your development database as precious. Need to test something destructive? Just do it. Switch to another branch and you're back to clean state. Switch back and your destructive changes are still there, isolated.
It sounds small. But removing that mental overhead adds up. Every branch switch that doesn't require database thinking is a small win. Those wins compound.
The Tradeoffs
Nothing is free.
Disk space is the obvious one. Each branch database is a full copy. If your development database is 500MB, ten branches means 5GB. For most projects this is fine. Disk is cheap and you can prune regularly.
Server restarts are required. The database name is determined when Rails boots (ERB in database.yml evaluates once). Switch branches without restarting and you're still connected to the old database. This catches people at first. If you're using bin/dev or Overmind, the restart is quick. Some teams wire up a post-checkout git hook to restart automatically. You get used to it either way.
PostgreSQL only. The cloning uses pg_dump and psql. Supporting other databases would mean different tooling for each. PostgreSQL covers most Rails projects, so that's where we started. If you're running Postgres in Docker, it just works. BranchDb talks to whatever Postgres your database.yml points to. No special volume handling needed.
Getting Started
Update database.yml:
Run rails db:prepare. You now have a branch-specific database.
That's it. No configuration required for most projects. If your main branch is called master instead of main, there's a config option for that. If you need shorter database names, you can limit the branch suffix length. But the defaults work for most teams.
What's Next
BranchDb solves the core problem, but there's more we want to build.
SQLite and MySQL support. Right now it's PostgreSQL only. We want to abstract the cloning layer so the same workflow works regardless of your database. SQLite would be trivial (it's just a file copy). MySQL needs mysqldump integration.
Standalone clone task. Sometimes you want to manually clone from a specific branch: rails db:branch:clone FROM=feature-x. Useful when the automatic parent detection isn't what you need.
Database info task. rails db:branch:info to show your current branch, database name, size on disk, and detected parent. Quick sanity check without digging through configs.
Clone progress indicator. For large databases, pg_dump | psql can take a while. Visual feedback would help.
Disk usage report. rails db:branch:list --size to see how much space each branch database is consuming. Makes cleanup decisions easier.
If any of these would help your workflow, open an issue. We're building based on what teams actually need.
The Bigger Point
Developer tools should remove friction, not add process.
The database-per-branch pattern isn't new. What's new is making it automatic. Making it the default behavior rather than something you have to remember to do.
Most developers won't manually create branch databases. The friction is too high for a problem that feels minor (until it isn't). But if the right thing happens automatically? Everyone benefits.
That's the bar for developer tooling. Not "possible if you follow these steps." Automatic. Invisible. Just works.
BranchDb tries to meet that bar. You install it, update one config file, and forget about it. Branch databases just happen. Cleanup is one command away.
The best tools disappear into your workflow. You stop noticing them because they stop causing problems.
That's the goal.
