Running a Patched Ruby on Heroku

Ghent,

You use a PaaS because you want all the underlying infrastructure and configuration of your application to be hidden from you. However, there are times when you are forced to look deeper into the stack. In this article I want to share how simple it is to run a patched version of Ruby on Heroku.

Context

It all started while trying to upgrade Ruby in an application. Unfortunately, every newer version I tried made the application break. After some searching around, I came across a bug report from 3 years ago in Ruby upstream.

The issue was actually not in Ruby but in Onigmo, the regular expressions library that Ruby uses under the hood. All versions since 2.4 where affected i.e. all supported versions including 2.5.8, 2.6.6 and 2.7.1 at the moment of writing. Lucky for me, Onigmo had been patched upstream but the patch will only land in Ruby 2.7 later this year.

This meant that, I was going to have to patch Ruby myself. For local development this is not a big deal, but I wasn’t sure if it was possible to do on Heroku. I remembered from CloudFoundry and Jenkins-X that the part of the platform taking care of the build and installation of the language were the buildpacks, so I decided to investigate about buildpacks on Heroku.

Heroku’s Ruby Buildpack

Heroku’s Ruby buildpack is used to run your application whenever there’s a Gemfile and Gemfile.lock file. From parsing these, it figures out which version of Ruby it’s meant to use.

Once it knows which version of Ruby to install, it runs bin/support/download_ruby, to download a pre built package and extracts it to be available for execution to your application. As a quick hack, I decided to modify this file to do what I did in my development environment to patch Ruby.

  1. First download the Ruby source code from upstream instead of the pre built version by Heroku.

    curl --fail --silent --location -o /tmp/ruby-2.6.6.tar.gz https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.6.tar.gz
    tar xzf /tmp/ruby-2.6.6.tar.gz -C /tmp/src
    cd /tmp/src/ruby-2.6.6
    
  2. Then apply a patch from a file I placed under bin/support/ (probably not the best place but OK while I was figuring things out).

    patch < "$BIN_DIR/support/onigmo-fix.diff"
    
  3. And finally build and install Ruby

    autoconf
    ./configure --disable-install-doc --prefix "$RUBY_BOOTSTRAP_DIR" --enable-load-relative --enable-shared
    make
    make install
    

You can find an unpolished but working version of what I did here

Using the Buildpack in Your Application

Now all that is left is to tell your application to use your custom buildpack instead of Heroku’s supported one. You can do this in the command line by running

heroku buildpacks:set https://github.com/some/buildpack.git -a myapp

Or by adding a file called app.json at the root directory of your application sources (not in the buildpack sources). I ended up using this form since I prefer to have as much of the platform configuration in code.

{
  "environments": {
    "staging": {
      "addons": ["heroku-postgresql:hobby-dev"],
      "buildpacks": [
        {
          "url": "https://github.com/some/buildpack.git"
        }
      ]
    }
  }
}

Now every time a deployment is made to this environment, the Ruby application will download the Ruby sources, patch, build and install them.

This of course is not very optimal since you’ll be wasting a lot of time building Ruby. Instead you should do something similar to what Heroku is doing by pre building the patched version of Ruby, and downloading it from an S3 bucket. {: .notice–warning}

Conclusion

Using a patched version of Ruby comes with a heavy price tag, the maintenance. You should still apply updates until that patch is fixed upstream (at least security updates). And you also need to use the patched version in all your environments e.g. production, staging, et al. including your CI. Whether all this extra work is worth it, is something you’ll need to analyze. In the cases when the benefits outweigh the costs, it’s great to know that you don’t have to give up all the benefits of a platform like Heroku to run your own version of Ruby.


tagged: Heroku PaaS Ruby

Related posts

The contents for this page are licensed CC-BY-SA.