The go-to resource for upgrading Ruby, Rails, and your dependencies.

Ruby Upgrade Guide

Step-by-step instructions on keeping your Ruby version up to date.

The Necessity of Upgrades

In classical antiquity, the Greek philosopher Plutarch recorded a thought experiment about a ship belonging to the mythical founder of Athens, Theseus. The ship was preserved by the Athenians for centuries, but over time, as its wooden planks began to rot, they were individually replaced with new, stronger timber. Plutarch asked: if every single plank was eventually replaced, was it still the same ship?

This philosophical puzzle, known as the Ship of Theseus, applies surprisingly well to software development. Over the lifespan of a durable application, we often replace its libraries, its frameworks, and even its underlying runtime. We upgrade the ship’s planks to keep it from sinking, even as it continues to sail.

Historically, upgrading a programming language’s runtime was often viewed with the same enthusiasm as navigating a storm. The infamous transition from Ruby 1.8 to 1.9, for example, introduced pervasive encoding changes that left many development teams wrestling with broken strings for weeks.

Strictly speaking, you do not have to upgrade Ruby immediately upon a new release — at least not if your application is stable and isolated. However, delaying upgrades indefinitely incurs a compounding technical debt. Older versions eventually stop receiving security patches, leaving applications vulnerable. Furthermore, every major Ruby release — such as 3.1, 3.2, or 3.3 — introduces significant performance enhancements, including the YJIT compiler. We want our applications to be secure and performant; therefore, establishing a routine upgrade process is an essential part of durable programming.

Before we get into the mechanics of the upgrade, though, it is wise to ensure that your application’s test suite is currently passing. Upgrading Ruby while dealing with preexisting failing tests makes it incredibly difficult to isolate which problems were caused by the new Ruby version. Additionally, before you use any of the commands in this guide, it’s wise to ensure the latest ‘known good’ version of your code is committed to source control.

Choosing a Version Manager

There are a number of different approaches to managing Ruby versions on a development machine; depending on the particular circumstances you find yourself in, one of them may be more useful than the others.

The first is rbenv; this is my preferred method for projects that primarily rely on Ruby. It is lightweight, does not override shell commands unnecessarily, and adheres to the Unix philosophy of doing one thing well.

The second is asdf, which manages multiple languages (like Node.js, Elixir, and Ruby) through a single interface. This option, though, will often make more sense if you work on polyglot applications.

A third, more recent option is mise, which acts as a faster, Rust-based alternative to asdf.

For our examples here, we will use rbenv, though the general concepts apply regardless of your choice.

The Upgrade Process

1.Update the Version Manager

First, we need to ensure our version manager knows about the latest Ruby releases. If we try to install a newly released Ruby version using an outdated version manager, it will likely fail.

For rbenv on macOS using Homebrew, we can update it like this:

$ brew upgrade rbenv ruby-build

Alternatively, if you are on a Debian-based system like Ubuntu and installed rbenv via git, you might update it by pulling the latest changes:

$ cd ~/.rbenv/plugins/ruby-build && git pull

One may wonder: if rbenv manages Ruby versions, why do we need to upgrade ruby-build alongside it? The answer is straightforward: ruby-build contains the actual definitions and scripts for how to download and compile each specific Ruby release. Without an updated ruby-build, rbenv remains completely unaware that newer versions — like 3.3.4 — even exist.

2.Install the New Ruby Version

With our tools updated, we can proceed to install the target Ruby version. Let’s assume we want to install Ruby 3.3.4:

$ rbenv install 3.3.4
==> Downloading ruby-3.3.4.tar.gz...
-> https://cache.ruby-lang.org/pub/ruby/3.3/ruby-3.3.4.tar.gz
==> Installing ruby-3.3.4...
ruby-build: using readline from homebrew
ruby-build: using libyaml from homebrew
# ...snip...
==> Installed ruby-3.3.4 to /Users/david/.rbenv/versions/3.3.4

This process downloads the source code and compiles it. I’ve abbreviated the above output for the sake of brevity, but you also may notice that ruby-build automatically discovers dependencies like libyaml and readline from Homebrew. This is significant because it means ruby-build prevents us from having to manually compile all of Ruby’s underlying C dependencies. Depending on your machine, this might take a few minutes.

Additionally, note that the exact version numbers you see will likely vary — no doubt, shortly after you read this, a newer patch version than 3.3.4 will be available.

Of course, if you are using mise, the command would be mise install ruby@3.3.4 followed by mise use ruby@3.3.4.

3.Update Project Files

Next, we must instruct our project to use the newly installed version. Typically, this involves updating two files: .ruby-version and Gemfile.

Let’s update the .ruby-version file first:

$ echo "3.3.4" > .ruby-version

Then, we need to open our Gemfile and update the ruby directive:

# Gemfile
source 'https://rubygems.org'

ruby '3.3.4'

# ...snip...

4.Bundle and Test

Now that the project specifies the new Ruby version, we need to reinstall our dependencies. This is necessary because gems with native C extensions — like nokogiri or pg — must be recompiled against the new Ruby environment.

$ bundle install
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
# ...snip...
Bundle complete! 114 Gemfile dependencies, 182 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

Once the installation completes, we can run our test suite to verify the application behaves correctly under the new Ruby version.

$ bundle exec rspec

If the tests pass, the upgrade is largely complete. If they fail, however, you will need to investigate. Common upgrade issues include keyword argument changes (particularly when upgrading to Ruby 3.0) or gems that have not yet been updated to support the new Ruby syntax.

For example, if you encounter an error like ArgumentError: wrong number of arguments, it is highly likely that a gem needs to be updated to support the newer keyword argument syntax. In those cases, updating the offending gems via bundle update <gem_name> is typically the next step.