I was working on a Rails application that used two PostgreSQL databases.
The primary database was owned by our application, so we could create it, run migrations, and modify the schema whenever we needed to.
The second database was different.
It belonged to another application, and our application only connected to it for reading data. We didn’t own its schema, and we weren’t responsible for running migrations against it.
Everything worked perfectly on my local machine, so I didn’t think much about it.
Then I pushed my changes, and the CI pipeline immediately failed.
The Problem
The errors weren’t very surprising once I looked at them.
Rails couldn’t find the tables it expected in the secondary database.
Locally, those tables already existed because I’d restored a copy of the database a while back. CI, on the other hand, started with a completely clean environment every time.
My first instinct was to treat it like any other Rails application and let Rails create everything.
bundle exec rails db:create
bundle exec rails db:migrate
That worked perfectly for the primary database.
It didn’t work for the secondary one.
Since our application didn’t own that database, it didn’t have migrations for it. Rails had no way of creating the schema that the tests expected.
Understanding the Real Problem
The issue wasn’t really with Rails.
It was with one assumption I had made.
I was treating both databases as if they belonged to the same application.
They didn’t.
One database was ours.
The other simply wasn’t.
Once I looked at it that way, trying to migrate the second database stopped making sense.
The schema already existed somewhere else. What the test environment actually needed wasn’t migrations—it needed a copy of that schema.
Restoring the Database Instead
We already had a PostgreSQL dump of the secondary database that we used during development, so I decided to use the same approach in CI.
The primary database was still created and migrated normally.
RAILS_ENV=test bundle exec rails db:create
RAILS_ENV=test bundle exec rails db:migrate
For the secondary database, instead of trying to run migrations that didn’t exist, I restored the database from a dump.
psql -U postgres \
-d secondary_test_db \
< db/backups/secondary_test_dump.sql
That gave the test environment exactly the same schema that existed in production, without pretending our application owned it.
The CI workflow ended up looking something like this:
- name: Setup Primary Database
run: |
bundle exec rails db:create
bundle exec rails db:migrate
- name: Restore Secondary Database
run: |
psql -U postgres \
-d secondary_test_db \
< db/backups/secondary_test_dump.sql
- name: Run Tests
run: bundle exec rspec
The Outcome
After making that change, the pipeline became stable.
The tests behaved the same way they did on my local machine, and we stopped seeing failures caused by missing tables in the secondary database.
More importantly, the CI setup now reflected how the application actually worked instead of forcing every database to follow the same setup process.
Looking Back
This was one of those bugs where I made it harder than it needed to be.
I spent quite a bit of time trying to figure out how to make Rails create and migrate the secondary database before realizing that Rails was never supposed to do that in the first place.
The moment I stopped treating it like one of our own databases, everything else fell into place.
Sometimes the hardest part isn’t finding the solution—it’s questioning the assumption you’ve been working with from the beginning.