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.
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
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).
- Most features
- Automatically installs system packages for many platforms
- Support the most Rubies, even very old ones (some with patches)
- Binary builds of some CRuby versions on some platforms
- Supports building
- The built Ruby can differ quite a bit from a standard build, which can cause extra issues
- Huge (25000 SLOC of Bash), hard to maintain, and due to that many bugs
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.
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
ruby-build does not install system packages for you,
but documents them on their wiki, which I find is much more future-proof.
- Actively maintained
- Small, 1390 SLOC of Bash
- Supports building
- Does not install system packages on its own (but that is more future-proof, and building Rubies tend to fail clearly when there is missing dependency)
ruby-install is the simplest Ruby installer.
It has an interesting design where installing new versions does not require to update
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-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.
- Tiny, 866 SLOC of Bash
- Elegant design
- Super easy to install the latest Ruby releases, if they don’t require
- Does not support
- Infrequent releases
- Users are not used to update
- Cuts a few corners in the name of simplicity
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.
- Most features
- Loads thousands of lines of Bash in your shell
- Many features which are of little use nowadays
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_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
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
- Just works
- Also works for non-Bash shells like
- Reliable, does not mess with
- Well maintained, least number of open issues
- Small, 1500 SLOC of Bash
- Small overhead on startup (~11 ms) due to shims
which ruby-related-executableis not enough to show the actual executable path, one needs
chruby does one additional thing which is to set
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
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
- Very simple and minimal, 149 SLOC of Bash
GEM_HOMEincorrectly on non-CRuby
- Requires to be loaded in the shell (there is chruby-fish though)
- Too rare releases, not addressing bugs promptly
Conclusion and Recommendation
- I recommend to use
ruby-buildover RVM or
ruby-installas IMHO it is a better Ruby installer in about every way.
- I recommend to use
rbenvover RVM or
chrubyas it is the only Ruby switcher not messing with gem paths.
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
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).