standalone sinatra jar with jruby

For Fauna, we needed to migrate the website (a Sinatra app) from Heroku to our own servers in AWS us-west-2. I wanted to get off MRI and bundle it up as a JAR as part of this process.

dad, why

We already deploy the core database as a monolithic JAR, so it made sense to deploy the website as a monolithic JAR as well. This conforms to our constraint-driven development methodology.

We also wanted to avoid having to set up a J2EE webserver to host a single WAR, but rather stick to a self-contained JVM per app. It turned out to be within the realm of straightforward.

the jar

Building a JAR from a Rack app requires three things:

  1. Put your code into /lib so Warbler adds it to the $LOAD_PATH.
  2. Rename your rackup so Warbler can’t find it and builds a JAR instead of a WAR. I put mine at /config/web.ru.
  3. Copy the startup script from your webserver into /bin. (I wanted to use Thick, since we already use Netty, but ultimately Puma worked best.)

Now you can run jruby -S warbler and get a dependency-free JAR with a bundled app, webserver, and copy of JRuby.

the deployment

Deployment is a little strange because Warbler/JRuby do not expose the JAR contents as a true filesystem.

Basic file operations like File.read and require will work, but Rack::File does not because it relies on send_file. Neither does OpenSSL certificate loading. I tried various workarounds but ended up having the deployment script (Ansible) unzip the public folder from the JAR and managed Sinatra’s :public_folder with an environment variable.

You may think your local JAR is working fine, but move it into an empty directory and try it from there before you deploy. Your app may be picking up the local filesystem and not the JAR for various non-Ruby dependencies. The rackup file suffered from this issue also and needed to be unpacked.

your turn

I put a stub app on Github so you can try it out. JAR away and forget about the Linux/rbenv/nginx/passenger flavor of the week. It doesn’t affect you!

conclusion

JARs are nice. JRuby needs to implement a lower-level file interface for the JAR contents, though. I’d love to see JAR support robustly embedded in JRuby itself.