Running old Ruby versions on Apple Silicon by fully emulating x86-64

Chad Pytel

I’ve been upgrading an old Rails 3.2 application to Rails 7 (which you can watch on our live stream), and one of the first stumbling blocks I hit was that Ruby 2.0, the version that the app was currently running on, doesn’t run at all on Apple Silicon computers. This wasn’t all that surprising given Ruby 2.0 came out in February of 2013 and is no longer supported. The first version of Ruby that worked out of the box on Apple Silicon without Rosetta was 2.6.10, released in April of 2022.

I know from experience upgrading Ruby and Rails apps that it’s best to work in small steps. Otherwise you run the risk of too many things breaking all at once and not being able to recover without significant effort, so I was very reluctant to jump all the way to Ruby 2.7.6 and the version of Rails that would support that (Rails 6).

However, I haven’t had an Intel-based machine for local development for several years now, so I found myself unable to actually do the upgrade on my local development computer, which is less than ideal.

I first explored whether I could instead virtualize an arm64 Linux environment that would support Ruby version 2.0. However, I was unable to successfully compile some Ruby gems, which my research showed was a known, unresolved issue.

The virtualization technology built into macOS and Apple Silicon has great performance, but only supports virtualizing ARM instances. In my prior limited experience, x86 emulation has been too slow to use.

I was surprised to find that the software I was already using for virtualization UTM (which is built on top of QEMU), had support for full x86-64 virtualization, and to see reports online that it was more usable than expected.

I decided to give it a try, and couldn’t believe that an hour later, I had a fully emulated x86-64 instance running Ubuntu 22.

Using this instance, I was able to set up asdf (our preferred multi-Ruby manager) and download, compile, and install Ruby 2.0 and was up and running for local development. Not only was it working, but I was shocked to see that from an SSH console, it was fast enough to actually work on.

You can also see this in action on my recent live stream episode. All the Ruby you see running there is on the emulated x86-64 Ubuntu instance. I’m using Visual Studio Code connected to the instance via SSH with the Visual Studio Code Remote - SSH extension, which itself installs software on the instance that runs well.

Ubuntu and a remotely connected Visual Studio

In general, the slowest thing is compiling software, especially different versions of Ruby. To save time, I created a list of the Ruby versions I wanted and downloaded and compiled them in the background in advance, so I didn’t have to wait when I needed them.

Here is how I’ve configured UTM for good performance of an x86 instance:

  • RAM: 8GB
  • CPU: I left this default to avoid issues, but you might also try “Enables all features supported by the accelerator in the current host (max)”
  • Force Multicore: On

UTM Configuration

If you find yourself in a situation where you need to run an older version of Ruby on Apple Silicon, this might be a solution for you.

Want to follow along and help with the upgrade from Rails 3.2 to Rails 7? Join us on the live stream. The next one is this Thursday, October 5th.