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.


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:

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:

Building the executable

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

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.

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:

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.

Join the Conversation


  1. Hi Josef! Nice to know ruby-packer is working. I got an error though when trying to build your invoice_printer as single Ruby executable. I think it has to do with the ruby-packer, because I got the same error when building my ruby script. I try to search google, and the results are errors related with ruby installation using rvm. I am really curious what has gone wrong. This is the error message. Does it ring a bell? Thanks in advance.

    In file included from openssl_missing.c:21:0:
    openssl_missing.h:133:41: warning: type defaults to ‘int’ in declaration of ‘X509_REQ’ [-Wimplicit-int]
    void ossl_X509_REQ_get0_signature(const X509_REQ *, const ASN1_BIT_STRING **, const X509_ALGOR **);
    openssl_missing.h:133:50: error: expected ‘;’, ‘,’ or ‘)’ before ‘*’ token
    void ossl_X509_REQ_get0_signature(const X509_REQ *, const ASN1_BIT_STRING **, const X509_ALGOR **);
    openssl_missing.c:131:1: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘{’ token
    openssl_missing.c:143:1: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘{’ token
    openssl_missing.c:155:1: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘{’ token
    openssl_missing.c:165:36: warning: type defaults to ‘int’ in declaration of ‘X509_REQ’ [-Wimplicit-int]
    ossl_X509_REQ_get0_signature(const X509_REQ *req, const ASN1_BIT_STRING **psig,
    openssl_missing.c:165:45: error: expected ‘;’, ‘,’ or ‘)’ before ‘*’ token
    ossl_X509_REQ_get0_signature(const X509_REQ *req, const ASN1_BIT_STRING **psig,
    In file included from /tmp/rubyc/openssl/include/openssl/engine.h:23:0,
    from openssl_missing.c:14:
    /tmp/rubyc/openssl/include/openssl/bn.h:285:1: error: old-style parameter declarations in prototyped function definition
    DEPRECATEDIN_0_9_8(BIGNUM *BN_generate_prime(BIGNUM *ret, int bits, int safe,
    openssl_missing.c:172:1: error: expected ‘{’ at end of input
    Makefile:304: recipe for target ‘openssl_missing.o’ failed
    make[2]: *** [openssl_missing.o] Error 1
    make[2]: Leaving directory ‘/tmp/rubyc/ruby-2.4.1-0.4.0/ext/openssl’ recipe for target ‘ext/openssl/all’ failed
    make[1]: *** [ext/openssl/all] Error 2
    make[1]: Leaving directory ‘/tmp/rubyc/ruby-2.4.1-0.4.0’ recipe for target ‘build-ext’ failed
    make: *** [build-ext] Error 2
    Failed running [{“CI”=>”true”, “ENCLOSE_IO_USE_ORIGINAL_RUBY”=>”1”, “CFLAGS”=>” -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe -I/tmp/rubyc/zlib -I/tmp/rubyc/openssl/include -I/tmp/rubyc/gdbm/build/include -I/tmp/rubyc/yaml/build/include -I/tmp/rubyc/libffi/build/lib/libffi-3.2.1/include -I/tmp/rubyc/ncurses/build/include -I/tmp/rubyc/readline/build/include “, “LDFLAGS”=>” -L/tmp/rubyc/zlib /tmp/rubyc/zlib/libz.a -L/tmp/rubyc/openssl -L/tmp/rubyc/gdbm/build/lib -L/tmp/rubyc/yaml/build/lib -L/tmp/rubyc/libffi/build/lib -L/tmp/rubyc/ncurses/build/lib -L/tmp/rubyc/readline/build/lib “, “ENCLOSE_IO_RUBYC_1ST_PASS”=>”1”, “ENCLOSE_IO_RUBYC_2ND_PASS”=>nil}, “make -j4 -j1”]

    1. I didn’t come across any issue like this. I suggest to open an upstream issue and put information about your environment there. I don’t think it’s RVM issue, ruby-packer packages its own Ruby version. Maybe you can try the trunk/master version that already has Ruby 2.5. I should have mention though that I am running Fedora 27 (and system Ruby).

      1. I have put an issue at ruby-packer repo. I have also try the trunk/master version and encounter another error. It’s related with miniruby now. Not sure what it is. I am running Ubuntu 16.04.

        linking miniruby
        autoupdate_autoupdate.o: In function autoupdate':
        /tmp/rubyc/ruby-2.5.1-0.5.0/autoupdate_autoupdate.c:957: undefined reference to
        /tmp/rubyc/ruby-2.5.1-0.5.0/autoupdate_autoupdate.c:985: undefined reference to inflate'
        /tmp/rubyc/ruby-2.5.1-0.5.0/autoupdate_autoupdate.c:997: undefined reference to
        squash_decompress.o: In function sqfs_decompressor_zlib':
        /tmp/rubyc/ruby-2.5.1-0.5.0/squash_decompress.c:34: undefined reference to
        collect2: error: ld returned 1 exit status
        Makefile:231: recipe for target ‘miniruby’ failed
        make: *** [miniruby] Error 1
        Failed running [{“CI”=>”true”, “ENCLOSE_IO_USE_ORIGINAL_RUBY”=>”1”, “CFLAGS”=>” -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe -I/tmp/rubyc/local/include -I/tmp/rubyc/local/lib/libffi-3.2.1/include “, “LDFLAGS”=>””, “ENCLOSE_IO_RUBYC_1ST_PASS”=>”1”, “ENCLOSE_IO_RUBYC_2ND_PASS”=>nil}, “make -j4”]

        1. One way you can work around this is to install Vagrant, get Fedora box and do it there. Since it’s one time thing (publishing) I think it’s a viable strategy (having always one VM around where this is working).

          Of course that’s not the right fix for your issue, but if you are curious to try it for your gem that might work.

  2. Current versions of ruby-packer leave an insecure RPATH in the binaries, anyone with write permission to `/tmp/ (usually everyone) can hijack the account of anyone running an executable compiled by ruby-packer.

    Unless you manually set the temporary path used during build with the -d parameter to point to some usually non-writable directory such as /root.

Leave a comment

Leave a Reply to Josef Strzibny Cancel reply

Your email address will not be published. Required fields are marked *