Review: Ruby Installers and Ruby Switchers

In this post I review the most popular Ruby installers (making it easier to install a Ruby) and Ruby switchers (to switch between different Rubies conveniently).

I contributed to all 3 Ruby installers when adding support for TruffleRuby, and so I have experience both with their codebase and their usage.

I am not reviewing Ruby installers or switchers for Windows as I have no experience with them.

TLDR: feel free to jump to the Conclusion and Recommendation.

Ruby Installers


RVM is probably the oldest and certainly the Ruby installer with the most features. That’s actually a doubled-edged sword, as it means RVM has a much larger codebase than all the others (25000 SLOC of Bash), and that makes it hard to maintain and to keep correct (note: I’m not counting test SLOC). Some features like gemsets seem much less needed nowadays as there is Bundler.

To give an idea, when I added support for TruffleRuby in RVM, I had many uncertainties, and the large codebase plus a lot of global variables made it really tricky. As a result, there were multiple bugs and a lot more effort than for other Ruby installers. Also, RVM actually changes standard Ruby executables like rake, which I really dislike as a Ruby implementer because it tends to break things in subtle ways (this behavior was later disabled when installing TruffleRuby).

To be fair, RVM served me well at the time other Ruby installers/switchers did not exist.

RVM supports a lot of platforms, as well as ancient Rubies. More platforms can be nice for beginners, but platform code eventually always breaks due to incompatible changes in operating systems (e.g., package names).




ruby-build is the best-maintained Ruby installer. Active maintainers include @hsbt from the CRuby core team, others and myself.

It offers a wide choice of Ruby versions. It also offers -dev versions, which is incredibly useful to let users check Ruby implementation bug fixes, or simply to try the latest/fastest/best build of a Ruby implementation.

The design is really straightforward (one definition per Ruby version), and as a result it’s much more reliable. Users update ruby-build regularly as that is the way to get new versions. This is great to also ship bug fixes quickly. The release process is entirely automated (kudos to @mislav).

ruby-build does not install system packages for you, but documents them on their wiki, which I find is much more future-proof.




ruby-install is the simplest Ruby installer.

It has an interesting design where installing new versions does not require to update ruby-install. Instead, ruby-install --latest is able to fetch the latest versions from ruby-versions and install them. This is another double-edged sword. On one hand it’s very elegant, and super easy to update. On the other hand, if any other change is needed (e.g., different system packages, the download URL changed, etc), that actually requires ruby-install changes, and users seem less used to update ruby-install. ruby-versions only contains Ruby versions and checksums, not URLs or packages, which IMHO feels too limited.

As an example, even though I made a PR to address a changed dependency the day it was reported, it took 8 months to have a ruby-install release with that (to be fair, there was an easy workaround).

ruby-install does not support development builds by design, which is IMHO a big limitation.



Ruby Switchers


RVM is both a Ruby installer and Ruby switcher so it appears here too. The points above really apply to both so I will not repeat them here.

The gemset feature tends to IMHO add more complexity than it helps, by making switching a 2-dimensional operation (ruby, gemset) instead of just 1 dimension (ruby). Also gemsets have symlimks in their path (not expanded, that’s how RVM knows which gemset is active), and this can occasionally cause issues.




rbenv is a simple Ruby switcher. It uses small shims (executables) to always redirect to the currently-selected Ruby. There is an overhead of that, in the order of 11 ms on my machine (pretty small, considering that ruby -e 0 takes 38 ms on my machine for ruby-3.0.1). OTOH, it guarantees it will never pick the wrong Ruby which is obviously important.

For me, the killer feature of rbenv is it does not mess with RubyGems. Specifically, it does not set GEM_HOME or GEM_PATH (unlike the other Ruby switchers), and so the gems corresponding to the selected Ruby are always correctly used. That also means it expects that Rubies are installed in user-writable locations (or one uses gem install --user-install or set Bundler path), but I think that’s a reasonable assumption for a Ruby switcher.

asdf is a multi-language switcher with a very similar design to rbenv and it uses ruby-build.




chruby is a minimal Ruby switcher. In fact, it’s so small it fits in a 100 lines of Bash. After all, switching Rubies only requires changing PATH and that’s it.

Unfortunately, chruby does one additional thing which is to set GEM_HOME. That can be convenient to install gems when a Ruby is installed in a non user-writable location. Unfortunately the way it’s done is incorrect for non-CRuby. I made a PR to fix this but 18 months after it was merged there is still no release. So at this point I feel very disappointed about the maintenance of chruby.

I still like chruby though, and I use a branch which simply doesn’t set GEM_HOME. That’s just 75 SLOC of Bash, one cannot get much simpler for switching Rubies. This is probably the most future-proof Ruby switcher out there.

FWIW, I made another PR to chruby to use a similar approach, but still set GEM_HOME if the default Gem home is not writable. After lots of efforts, and even getting consensus on the related RubyGems issue for the general approach, the PR is still not merged so I have not much hope for that (maybe it is time to hard fork chruby …).



Conclusion and Recommendation

In summary:

The rbenv/ruby-build combo is fairly natural and clearly the best maintained.

Alternatively, you can try ruby-build + my branch of chruby which does not set GEM_HOME. This is what I use and it works perfectly from my experience.

I would recommend against RVM because of the sheer complexity and the huge codebase which is basically impossible to keep bug-free.

Note that while I might criticize various softwares in this blog post, I have a lot of respect for their authors, and I know it is a hard job to maintain such software (written in Bash, one of the worst programming languages).