Bundler Changed Where Your Canonical Ruby Information Lives: What You Need to Know

Listen to this article

Heroku bumped its Bundler version to 1.13.7 almost a month ago, and since then we've had a large number of support tickets opened, many a variant of the following:

Your Ruby version is <X>, but your Gemfile specified <Y>

I wanted to talk about why you might get this error while deploying to Heroku, and what you can do about it, along with some bonus features provided by the new Bundler version.

First off, why are you getting this error? On Heroku in our Ruby Version docs, we mention that you can use a Ruby directive in your Gemfile to specify a version of Ruby. For example if you wanted 2.3.3 then you would need this:

# Gemfile ruby "2.3.3"

This is still the right way to specify a version, however recent versions of Bundler introduced a cool new feature. To understand why this bug happens you need to understand how the feature works.

If you have people on your team who want to use a more recent version of Ruby, for example say Ruby 2.4.0 locally, but you don't want to force everyone to use that version you can use a Ruby version operator.

# Gemfile ruby "~> 2.3"

I don't recommend you do this since "2.3" isn't a technically valid version of Ruby. I recommend using full Ruby versions in the version specifier; so if you don't have a Ruby version in your Gemfile.lock bundle platform --ruby will still return a valid Ruby version. You can use multiple version declarations just like in a gem for example: ruby '>= 2.3.3', '< 2.5' .

This says that any version of Ruby up until 3.0 is valid. This feature came in Bundler 1.12 but wasn't made available on Heroku until Bundler 1.13.7.

In addition to the ability to specify a Ruby version specifier, Bundler also introduced locking the actual Ruby version in the Gemfile.lock :

# Gemfile.lock RUBY VERSION ruby 2.3.3p222

When you run the command:

$ bundle platform --ruby ruby 2.3.3p222

You'll get the value from your Gemfile.lock rather than the version specifier from your Gemfile . This is to provide you with development/production parity. To get that Ruby version in your Gemfile.lock you have to run bundle install with the same version of Ruby locally, which means when you deploy you'll be using a version of Ruby you use locally.

Did you know this is actually how Heroku gets your Ruby version? We run the bundle platform --ruby command against your app.

So while the version specifier tells bundler what version ranges are "valid" the version in the Gemfile.lock is considered to be canonical.

So if you were using the app before with the specifier ruby "~> 2.3" and you try to run it with Ruby 1.9.3 you'll get an error:

Your Ruby version is 1.9.3, but your Gemfile specified ~> 2.3

This is the primary intent of the bundler feature, to prevent you from accidentally using a version of Ruby that may or may not be valid with the app. However if Heroku gets the Ruby version from bundle platform --ruby and that comes from the Gemfile and Gemfile.lock , how could you ever be running a version of Ruby on Heroku different from the version specified in your Gemfile ?

One of the reasons we didn't support Bundler 1.12 was due to a bug that allowed incompatible Gemfile and Gemfile.lock Ruby versions. I reported the issue, and the bundler team did an amazing job patching it and releasing the fix in 1.13.

What I didn't consider after is that people might still be using older bundler versions locally.

So what is happening is that people will update the Ruby version specified in their Gemfile without running bundle install so their Gemfile.lock does not get updated. Then they push to Heroku and it breaks. Or they're using an older version of Bundler and their Gemfile.lock is using an incompatible version of Ruby locally but isn't raising any errors. Then they push to Heroku and it breaks.

So if you're getting this error on Heroku, run this command locally to make sure your Bundler is up to date:

$ gem install bundler Successfully installed bundler-1.13.7 1 gem installed Installing ri documentation for bundler-1.13.7... Installing RDoc documentation for bundler-1.13.7...

Even if you haven't hit this bug yet, go ahead and make sure you're on a recent version of Bundler right now. Once you've done that run:

$ bundle install

If you've already got a Ruby version in your Gemfile.lock you'll need to run

$ bundle update --ruby

This will insert the same version of Ruby you are using locally into your Gemfile.lock .

If you get the exception locally Your Ruby version is <X>, but your Gemfile specified <Y> it means you either need to update your Gemfile to point at your version of Ruby, or update your locally installed version of Ruby to match your Gemfile .

Once you've got everything working, make sure you commit it to git

$ git add Gemfile.lock $ git commit -m "Fix Ruby version"

Now you're ready to git push heroku master and things should work.

When these type of unexpected problems creep up on customers we try to do as much as we can to make the process easy for customers to understand the problem, and the fix. After seeing a few tickets come in, the information was shared internally with our support department (they're great by the way). Recently I added documentation to Dev Center to document this explicit problem. I've also added some checks in the buildpack to give users a warning that points them to the docs. This is the best case scenario where not only can we document the problem, and the fix, but also add docs directly to the buildpack so you get it when you need it.

I also wanted to blog about it to help people wrap their minds around the fact that the Gemfile is no longer the canonical source of the exact Ruby version, but instead the Gemfile.lock is. While the Gemfile holds the Ruby version specifier that declares a range of ruby versions that are valid with your app, the Gemfile.lock holds the canonical Ruby version of your app.

As Ruby developers we have one of the best (if not the best) dependency managers in bundler. I'm excited for more people to start using version specifiers if the need arises for their app and I'm excited to support this feature on Heroku.