Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say Hello
Test Double logo
Menu
Services
BackGrid of dots icon
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say hello
Developers
Developers
Developers
Rails upgrades

Dual booting a Rails upgrade with Docker and CI

Bootboot is a great tool for dual booting your Rails upgrade, but it doesn't play well with deployment environments running Rails 4.x. In this blog post we'll show you how you can leverage Docker to effectively get Bootboot to work in these environments.
Ali Ibrahim
|
January 15, 2023
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

So you’re working on a Rails upgrade in a pretty big app that has lots of active development. The app’s pretty far behind Rails versions—let’s say it’s on Rails 4.2. It’s tempting to upgrade straight to the latest Rails version, but you decide to take an incremental approach and upgrade to the next point release, Rails 5.0.

You have a feeling this upgrade is gonna take a while, and instead of trying to maintain a long-lived branch, you choose to go with a dual booting strategy. You do this with the Bootboot Bundler plugin and have dual booting setup in no time. You open a PR with the changes and wait for a green build, but before any tests are run, you see a weird error:

Unable to find a spec satisfying bootboot (>= 0) in the set. Perhaps the lockfile is corrupted?

WAT.

Bootboot doesn’t play well with older dependencies

You’re most likely seeing this error because your CI environment is installing gems with Bundler’s deployment flag. One of the things the deployment flag does is freeze the Gemfile against gem versions specified in Gemfile.lock so gems can’t be changed (you generally don’t want unexpected gem changes to happen as part of the deploy process). Since Bootboot is a Bundler plugin, it gets installed every time the Gemfile is evaluated. The Gemfile isn’t frozen in development, so Bootboot gets installed with no problem. It is frozen on CI, however, so you get an error. 😕

Here’s the good news—this is a known issue! And it’s been fixed!! 🥳 Bundler has been updated to allow plugin installs when the Gemfile is frozen. Now we just need to upgrade Bundler, right?

Here’s the bad news. That change was released in Bundler 2.0.2, but you can’t use Bundler > 2.0 on Rails 4.x. 😭

[Note: The Bundler restriction is relaxed on Rails 5.0, allowing Rails to be used with Bundler 2+. If you’re on Rails 5+, try upgrading Bundler to get past this issue. But if you can’t upgrade Bundler, read on!]

To workaround this issue, the Bootboot README recommends disabling Bootboot when the Gemfile is frozen. You’d do this in the Gemfile with

plugin 'bootboot' unless Bundler.settings[:frozen]

This gets rid of the error, but now you won’t be able to use Bootboot in environments where gems are frozen. This kinda defeats the point of using Bootboot though, right? You’ll be able to dual boot Rails 4.2 and Rails 5.0 in development, but you won’t have a way to do it in CI, staging, production, etc.

Now what?

Docker FTW

If your deployment environments are Dockerized, there are a few tricks you can use to get this thing working. To demonstrate, we’ll use the following Dockerfile as a reference.

[Note: Rails will soon ship with more Docker support. New Rails apps will be generated with a Dockerfile that can be used as a starting point for production environments. There’s also a new Docked Rails CLI for local development in Docker.]

# syntax=docker/dockerfile:1
FROM ruby:2.7
WORKDIR /myapp

# Install Bundler
RUN gem install bundler:1.17.3

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install --deployment

# Copy application code
COPY . .

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Configure the main process to run when running the image
CMD ["bin/rails", "server", "-b", "0.0.0.0"]

In the first part of the Dockerfile, we’re copying the Gemfile and Gemfile.lock to the container’s working directory and installing the app’s gems with the deployment flag.

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install --deployment

We still have the workaround that prevents Bootboot from being installed when the Gemfile is frozen. So

  1. Bootboot won’t be installed in the container
  2. The container currently only has the Rails 4.2 gems installed

Now we’ll change the Dockerfile, so the container also has Rails 5.0 gems installed. If Bootboot were available, we’d only need to add

COPY Gemfile_next.lock ./
RUN DEPENDENCIES_NEXT=1 bundle install --deployment

When DEPENDENCIES_NEXT is set, Bootboot will tell Bundler to use the Rails 5.0 gems by

  1. using the Rails 5.0 gems specified in the if ENV[DEPENDENCIES_NEXT] section of the Gemfile
  2. resolving Rails 5.0 gem versions against Gemfile_next.lock

Without Bootboot, the first step will still work as expected because it’s not specific to Bootboot. Bundler will evaluate whatever Ruby code is in the Gemfile. The second step will fail though. Without Bootboot, Bundler assumes the lockfile is named Gemfile.lock as it matches Gemfile. Bundler would then resolve the Rails 5.0 gem versions against Gemfile.lock that has Rails 4.2 gem versions. It ain’t gonna work.

We need to tell Bundler to use Gemfile_next.lock and not Gemfile.lock when resolving Rails 5.0 gem versions. Bundler doesn’t expose a way to specify what lockfile to use, but we can tell it what Gemfile to use. 😁

We’ll add a few lines to the Dockerfile. After installing the Rails 4.2 gems we’ll do

COPY Gemfile_next.lock ./
COPY Gemfile ./Gemfile_next
RUN DEPENDENCIES_NEXT=1 BUNDLE_GEMFILE=Gemfile_next bundle install --deployment

This will add two files to the container: a copy of Gemfile_next.lock and a copy of the Gemfile named Gemfile_next. Then, we’ll get Bundler to use Gemfile_next.lock by telling it that the BUNDLE_GEMFILE is Gemfile_next. And now our container will have both Rails 4.2 and Rails 5.0 gems installed. 🥳

Finally, we need to expose a way to run the containers with Rails 5.0 gems. Looking back at our Dockerfile, we can see there’s an entrypoint.sh script that gets run every time the container starts.

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

In entrypoint.sh we’ll use DEPENDENCIES_NEXT to set BUNDLE_GEMFILE before executing the container’s command.

#!/bin/bash

if [[ -v DEPENDENCIES_NEXT ]]; then
  export BUNDLE_GEMFILE=Gemfile_next
else
  export BUNDLE_GEMFILE=Gemfile
fi

# Exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

This lets us abstract away the fact that Bootboot isn’t available in the containers. When DEPENDENCIES_NEXT is set, we’ll set BUNDLE_GEMFILE to Gemfile_next, so the Rails 5.0 gems get used. Otherwise, we’ll set BUNDLE_GEMFILE to Gemfile and use the Rails 4.2 gems by default.

And with that, we can effectively use Bootboot when the Gemfile is frozen on Rails 4.x (and pre-Bundler 2.0.2). 🎊

tl;dr: I want to use Bootboot on Rails 4.x and the Gemfile is frozen and I’m using Docker

Your Docker setup will probably be different, but the gist here is:

In the container setup

  1. Install the production gems as normal
  2. Make a copy of the Gemfile named Gemfile_next
  3. Install the Rails upgrade gems by setting BUNDLE_GEMFILE to Gemfile_next

When the container starts

  1. If DEPENDENCIES_NEXT is set, set BUNDLE_GEMFILE to Gemfile_next. Otherwise, set it to Gemfile.

All’s well that ends well

When working on Rails upgrades for big, old apps, you’ll likely run into situations like this where new things don’t work with old things. And it can be a very frustrating experience. But with a little bit of patience, fortitude, and creativity you can usually get the computers to do what you want ’em to do.

h/t to this Bootboot issue for giving me faith that there had to be a way to get this to work

Related Insights

🔗
Zero downtime Rails upgrades
🔗
Managing Ruby hashes in Rails upgrades
🔗
Guide to incremental Rails upgrades for smoother transitions
🔗
Why it’s worth outsourcing your Rails upgrades

Explore our insights

See all insights
Leadership
Leadership
Leadership
The business of AI: Solve real problems for real people

After participating in the Perplexity AI Business Fellowship, one thing became clear: the AI hype cycle is missing the business fundamentals. Here are 3 evidence-based insights from practitioners actually building or investing in AI solutions that solve real problems.

by
Cathy Colliver
Leadership
Leadership
Leadership
Pragmatic approaches to agentic coding for engineering leaders

Discover essential practices for AI agentic coding to enhance your team’s AI development learning and adoption, while avoiding common pitfalls of vibe coding.

by
A.J. Hekman
by
Aaron Gough
by
Alex Martin
by
Dave Mosher
by
David Lewis
Developers
Developers
Developers
16 things software developers believe, per a Justin Searls survey

Ruby on Rails developer Justin Searls made a personality quiz, and more than 7,000 software developers filled it out. Here's what it revealed.

by
Justin Searls
Letter art spelling out NEAT

Join the conversation

Technology is a means to an end: answers to very human questions. That’s why we created a community for developers and product managers.

Explore the community
Test Double Executive Leadership Team

Learn about our team

Like what we have to say about building great software and great teams?

Get to know us
Test Double company logo
Improving the way the world builds software.
What we do
Services OverviewSoftware DeliveryProduct ManagementLegacy ModernizationDevOpsUpgrade RailsTechnical RecruitmentTechnical Assessments
Who WE ARE
About UsCulture & CareersGreat CausesEDIOur TeamContact UsNews & AwardsN.E.A.T.
Resources
Case StudiesAll InsightsLeadership InsightsDeveloper InsightsProduct InsightsPairing & Office Hours
NEWSLETTER
Sign up hear about our latest innovations.
Your email has been added!
Oops! Something went wrong while submitting the form.
Standard Ruby badge
614.349.4279hello@testdouble.com
Privacy Policy
© 2020 Test Double. All Rights Reserved.