BranchDb
Automatic per-branch PostgreSQL databases for Rails development.
BranchDb eliminates database migration conflicts by giving each git branch its own isolated database. Switch branches freely without worrying about schema mismatches or losing development data.
Table of Contents
- The Problem
- The Solution
- Installation
- Configuration
- Usage
- How It Works
- Requirements
- Important Notes
- Troubleshooting
- Development
- Roadmap
- Contributing
- License
The Problem
Working on multiple feature branches with different migrations causes pain:
# On feature-a branch: Add a 'status' column
rails generate migration AddStatusToUsers status:string
rails db:migrate
# Switch to feature-b branch
git checkout feature-b
git status
# => modified: db/schema.rb <- Contains 'status' column from feature-a!
# Now your schema.rb has changes that don't belong to this branch
# Accidentally commit it? You've just mixed schema changes across branches
# Run db:migrate? Schema.rb still shows the foreign column
More complex migrations could cause the code to stop working like renaming tables, adding foreign keys, or removing columns!
The Solution
BranchDb automatically manages separate databases for each branch:
main branch → myapp_development_main
feature-auth → myapp_development_feature_auth
feature-payments → myapp_development_feature_payments
bugfix-login → myapp_development_bugfix_login
Each branch has its own isolated database with its own schema and data. Switch branches, restart your server, and you're working with the right database automatically.
Installation
Add BranchDb to your Gemfile:
group :development, :test do
gem 'branch_db'
endInstall and run the generator:
bundle install
rails generate branch_db:installUpdate your config/database.yml:
development:
<<: *default
database: <%= BranchDb.database_name('myapp_development') rescue 'myapp_development' %>
test:
<<: *default
database: <%= BranchDb.database_name('myapp_test') rescue 'myapp_test' %>Note: The
rescuefallback ensures production/staging environments work correctly since the gem is only loaded in development/test. Replacemyappwith your application name.
Initialize your first branch database:
rails db:prepareConfiguration
The generator creates config/initializers/branch_db.rb:
BranchDb.configure do |config|
# The name of your main/stable branch (default: 'main')
config.main_branch = 'main'
# Maximum length for branch name suffix (default: 33)
# PostgreSQL has a 63 character limit for database names
# Formula: base_name_length + 1 (underscore) + max_branch_length <= 63
config.max_branch_length = 33
# Database name suffixes for cleanup feature (default: '_development', '_test')
# Customize if your database names use different conventions
# config.development_suffix = '_development'
# config.test_suffix = '_test'
endConfiguration Options
| Option | Default | Description |
|---|---|---|
main_branch |
'main' |
Your primary branch name (used as fallback clone source) |
max_branch_length |
33 |
Max characters for branch suffix (prevents exceeding PostgreSQL's 63-char limit) |
development_suffix |
'_development' |
Suffix pattern for development databases |
test_suffix |
'_test' |
Suffix pattern for test databases |
Usage
Daily Workflow
BranchDb enhances Rails' built-in db:prepare command:
# Just use Rails' standard command - now branch-aware!
rails db:prepareWhat happens (development only):
- Checks if your branch's database exists and has schema
- If missing/empty and parent/main exists: clones from parent branch (or main as fallback)
- If on main branch or no source exists: defers to standard Rails behavior
- Rails then runs pending migrations and seeds as usual
Test databases use standard Rails behavior (schema load, no cloning):
RAILS_ENV=test rails db:prepareNote: Cloning only runs in development environment. All commands support Rails' multiple database feature.
Available Commands
| Command | Description |
|---|---|
rails db:prepare |
Rails' standard command, enhanced with cloning from parent/main |
rails db:branch:list |
List all branch databases |
rails db:branch:purge |
Remove all branch databases except current and main |
rails db:branch:prune |
Remove databases for branches that no longer exist in git |
Examples
# Starting work on a new feature branch
git checkout -b feature-new-thing
rails db:prepare # Clones from parent branch (or main as fallback)
rails server
# Switching to another branch
git checkout feature-other-thing
# Restart your Rails server to connect to the other database
rails server
# Purging all branch databases (keeps current and main only)
rails db:branch:purge
# => Found 5 database(s) to remove:
# => - myapp_development_feature_old
# => - myapp_test_feature_old
# => ...
# => Proceed with deletion? [y/N]
# Pruning databases for deleted git branches only
rails db:branch:prune
# => Found 2 database(s) to remove:
# => - myapp_development_merged_feature
# => - myapp_test_merged_feature
# => Proceed with deletion? [y/N]How It Works
Rails Integration
BranchDb enhances Rails' db:prepare task by adding a prerequisite that clones from the parent branch when needed. This means:
-
Zero learning curve - use
rails db:prepareas usual - Automatic cloning - new branch databases are cloned from their parent (or main as fallback)
- Rails handles the rest - migrations, seeds, and schema dumps work normally
Database Naming
BranchDb generates database names by combining your base name with a sanitized branch name:
Base name: myapp_development
Branch: feature/user-auth
Sanitized: feature_user_auth
Result: myapp_development_feature_user_auth
Branch names are sanitized: non-alphanumeric characters become underscores, and names are truncated to max_branch_length.
Cloning Process
When db:prepare detects a missing or empty database:
- Detects the parent branch to clone from (see below)
- Checks if the parent database exists; if not, falls back to main
-
If source exists: Creates the target database and uses
pg_dump | psqlfor efficient cloning -
If no source: Defers to Rails' standard
db:prepare(loads schema, runs migrations, seeds) -
On main branch: Defers to Rails' standard
db:prepare
Parent Branch Detection
BranchDb intelligently detects which branch you branched from and clones its database. This enables nested feature branch workflows:
main → feature-a → feature-a-child
When you create feature-a-child from feature-a, BranchDb will clone from feature-a's database (if it exists), not main.
Detection priority:
-
BRANCH_DB_PARENTenvironment variable (explicit override) - Git reflog analysis (finds the last "checkout: moving from X to current-branch")
- Configured
main_branch(fallback)
Fallback behavior: If the detected parent's database doesn't exist, BranchDb automatically falls back to the main branch database.
Override with environment variable:
# Force cloning from main, even if on a nested feature branch
BRANCH_DB_PARENT=main rails db:prepare
# Clone from a specific branch
BRANCH_DB_PARENT=feature-other rails db:preparePurge Safety
The purge command protects important databases:
- Current branch's development and test databases
- Main branch's development and test databases
- Databases with active connections (skipped with warning)
Requirements
- Ruby >= 3.2
- Rails >= 7.0
- PostgreSQL (any supported version)
-
PostgreSQL client tools in PATH:
-
psql- for database operations -
pg_dump- for cloning databases -
dropdb- for purge/prune operations
-
Verifying PostgreSQL Tools
which psql pg_dump dropdb
# Should output paths for all three toolsIf missing, install PostgreSQL client tools:
# macOS
brew install postgresql
# Ubuntu/Debian
sudo apt-get install postgresql-client
# Docker (add to your Dockerfile)
RUN apt-get update && apt-get install -y postgresql-clientImportant Notes
Server Restart Required
Database selection happens at Rails boot time (ERB in database.yml is evaluated once). After switching branches, restart your Rails server to connect to the correct database.
git checkout other-branch
# Must restart Rails to use other-branch's database
rails server # Now connected to myapp_development_other_branchDetached HEAD State
In detached HEAD state (e.g., git checkout abc123), BranchDb cannot determine a branch name. It falls back to using the base database name without a suffix. All detached HEAD checkouts share this database.
For CI environments, ensure you checkout an actual branch:
# CI script
git checkout $BRANCH_NAME # Not just the commit SHA
rails db:prepareDatabase Name Length
PostgreSQL limits database names to 63 characters. With default settings:
- Base name: up to 29 characters
- Underscore: 1 character
- Branch suffix: up to 33 characters
If your base name is longer, reduce max_branch_length accordingly.
Troubleshooting
"PostgreSQL tool 'X' not found in PATH"
Install PostgreSQL client tools (see Requirements).
"Could not connect to Postgres on port X"
Ensure PostgreSQL is running and accessible:
# Check if PostgreSQL is running
pg_isready -h localhost -p 5432
# For Docker users
docker ps | grep postgresDatabase not switching when I change branches
Remember to restart your Rails server after switching branches. The database name is determined at boot time.
Clone is slow for large databases
pg_dump | psql is already efficient, but for very large databases consider:
- Keeping your main branch database lean
- Using database-level compression
- Running cleanup regularly to remove old branch databases
Branch name too long
Long branch names are automatically truncated to max_branch_length (default: 33). Two branches with the same prefix might collide:
feature/very-long-descriptive-name-for-auth → _feature_very_long_descriptive_na
feature/very-long-descriptive-name-for-payments → _feature_very_long_descriptive_na # Same!
Use shorter branch names or increase max_branch_length (if your base name is short enough).
Development
Setup
git clone https://github.com/milkstrawai/branch_db.git
cd branch_db
bin/setupRunning Tests
# Run test suite
bundle exec rspec
# Run with coverage report
bundle exec rspec && open coverage/index.html
# Run linter
bundle exec rubocop
# Run both
bundle exec rakeTest Coverage
The project maintains high test coverage standards:
- Line coverage: 100%
- Branch coverage: 90%
Roadmap
Features we're considering for future releases:
- SQLite and MySQL support - Database adapter pattern for non-PostgreSQL databases
- Standalone clone task -
rails db:branch:clone FROM=branch-namefor manual cloning - Post-checkout git hook - Auto-restart Rails server on branch switch (Doable?)
- Database info task -
rails db:branch:infoshowing current branch, DB name, size, and parent - Clone progress indicator - Visual feedback for large database clones
- Disk usage report -
rails db:branch:list --sizeto show storage per branch
Have a feature request? Open an issue to discuss it!
Contributing
Contributions are welcome! Here's how you can help:
- Fork the repository
-
Create a feature branch (
git checkout -b feature/amazing-feature) -
Commit your changes (
git commit -m 'Add amazing feature') -
Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Guidelines
- Write tests for new features
- Follow existing code style (RuboCop will help)
- Update documentation as needed
- Keep commits focused and atomic
Reporting Issues
Found a bug? Please open an issue with:
- Ruby and Rails versions
- PostgreSQL version
- Steps to reproduce
- Expected vs actual behavior
License
BranchDb is available as open source under the terms of the MIT License.
Acknowledgments
Inspired by the pain of database migration conflicts and the joy of isolated development environments.