Making a Ruby executable with ruby-packer

You can make a single executable from your gem or even a Rails application. I just tried ruby-packer and it works as promised.

One of the things that I missed when writing a command line tool in Ruby was making a binary that is easy to distribute. Since Ruby is an interpreter we cannot just make a binary.

However, there are ways how to package Ruby interpreter and all the required gems together with your program as one distribution. For one there was Traveling Ruby project. Another good option is to look at how Vagrant is packaged (I did that when we were bringing Vagrant natively for Fedora back in the day). But neither approach felt quite right and the best way to distribute Ruby programs was to build them as RPM or DEB packages. That’s of course a lot of work and you end up with only one platform so not suitable for quick afternoon hacking unfortunately.

But then ruby-packer appeared on my radar and it got my hopes high! Today I finally had some time and tried to make a single executable for InvoicePrinter. Here is how it went.

Installation

First we need to install the prerequisites. The main one is SquashFS, a compressed read-only file system for Linux, the core idea behind ruby-packer. On Fedora we install squashfs-tools package:

$ sudo dnf install squashfs-tools

Then we need a C compiler, GNU Make and Ruby. If you are on Fedora like me and still don’t have those follow Fedora Developer Portal instructions.

Once done I locally fetched ruby-packer as mentioned in docs:

$ curl -L http://enclose.io/rubyc/rubyc-linux-x64.gz | gunzip > rubyc
$ chmod +x rubyc
$ ./rubyc --help

Building the executable

Everything went smoothly so without further ado let’s build InvoicePrinter as single Ruby executable:

$ ./rubyc --gem=invoice_printer --gem-version=1.2.0 invoice_printer --output invoice_printer

We are calling rubyc in a RubyGems mode by providing a gem name (--gem) and version (--gem-version), executable entry point and output.

After a while it produced invoice_printer executable (a.out in case the output name is not provided) and I was ready to give it a spin.

$ ./invoice_printer --help
Usage: invoice_printer [options]

Options:

    -l, --labels   labels as JSON
  -d, --document   document as JSON
     -s, --stamp   path to stamp
          --logo   path to logotype
          --font   path to font
     --page-size   letter or a4 (letter is the default)
  -f, --filename   output path
    -r, --render   directly render PDF stream (filename option will be ignored)

Works just as the gem version!

I am really pleasantly surprised. I now have 26.3 MB big working executable that I can distribute. The only small issue is that we have just one entry point, but InvoicePrinter comes with two (command line and server). To that end we have to create a separate program for InvoicePrinter Server:

$ ./rubyc --gem=invoice_printer --gem-version=1.2.0 invoice_printer_server --output invoice_printer_server

Conclusion: I can offer executables for my gems to make it easy to use for folks not having a Ruby runtime around. And it all took me just 10 minutes. ruby-packer surely needs a closer look.

Before you leave…

Would you like to learn how to deploy your own Ruby on Rails applications? I am writing a book for people like you.

Check it out →