Bundler and cross-platform development

The hazards of cross-platform development

Recently I helped a co-worker with getting Rails, Nginx, and Unicorn up and running in a Linux VM, using Capistrano to deploy the Rails application from the development box. While we got this to work in the end, it was not without pain. You see, we wanted the deployment to work regardless of the development platform.

The problems we ran into all revolved around Bundler, specifically that it wanted to use the entries in the Gemfile.lock file to determine what gems to install on the VM. This isn’t a ‘problem with Bundler’; it wants to use the Gemfile.lock to install the specific gem versions used during development, which is a desirable thing. The problem is that our stack used gems with native extensions that are different/don’t exist across the platforms involved.

As background, here’s the Capistrano file we started out with: deploy.rb.

Problem #1: Unicorn isn’t available for Windows

Development environment, who cares what we use to handle HTTP right? Just add something like the following to the Gemfile so Unicorn won’t install on Windows but will install on the VM and test using WEBrick locally:

platforms :ruby do gem 'unicorn' end 

Wrong. If you don’t install Unicorn on the development environment, it won’t be added to the Gemfile.lock file and so the server never installs Unicorn since it’s only looking at the Gemfile.lock entries.

The only way we were able to work around this was to add the following to the deploy.rb file used by Capistrano:

set :bundle_flags, "--no-deployment --quiet" 

This says it’s not a deployment, so install the gems based on the Gemfile rather than Gemfile.lock, and don’t return all the output of this operation. Not ideal, as it completely negates Bundlers “I’ve got a list of all your gem versions used in development” functionality but after searching around I couldn’t find a better solution.

Problem #2: .sh files not executable once deployed

This was fairly minor, but still important. The unicorn_init.sh file used to start/stop unicorn was not marked as executable once deployed to the Linux VM, so Unicorn wasn’t starting at the end of deployment. We resolved this by adding a “fix permissions” task to the deploy.rb file, and then invoking it after the “deploy:finalize_update” task. Here’s the code:

 desc "Fix permission"
task :fix_permissions, :roles => [ :app, :db, :web ] do
  run "chmod +x #{release_path}/config/unicorn_init.sh"

after "deploy:finalize_update", "deploy:fix_permissions"

The real problem: native modules

No, I’m not going on a rant about how native modules are evil or that you should make sure they work on all platforms. But be aware that you’re using them and that they may cause problems when someone decides to use your code on a different platform.

Note that this isn’t a Ruby/Bundler specific issue, as other languages such as Node.js have similar native code issues. Any time you have to leverage OS or hardware specific functionality, you limit the portability of your code.

Is there a better way?

For working with Bundler, not that I could find. I’d be interested if anyone has recommendations on dealing with the development and deployment of Rails applications in heterogeneous environments.

Comments (2)

  1. Fabio Akita says:

    Yes, there is much better way. First of all, never deploy systems such as Ruby and any other Unix/Posix derived platforms under Windows. It's bad practice, the ports are always worse than the original. So let's establish that you will deploy under Unix. There are hundreds of options, you have Heroku, you have bare bone machines at Amazon AWS EC2, Linode VPS, Rackspace Cloud Servers and so on. So there you can choose whatever you want.

    The problem now is that you probably have a Windows development machine. A nice Dell XPS or anything like that where you like to play games and also develop software. As I've said, you can easily install Ruby under Windows or for that matter any other platform such as Python, PHP, etc. But they are sub-optimal, lack several advanced packages that require a Unix toolchain.

    So use Unix/Linux. More specifically use Vagrant for development (http://www.vagrantup.com). You get the best of both worlds. You can use your favorite text editor under Windows (Notepad++ or even the great Sublime Text 2) and seamlessly run your projects under a pure Linux environment. No dependency problems, the entire toolchain at your disposal and, more importantly, the exactly same environment for development and production. That means zero headaches. Give it a try.

  2. Larry Franks says:

    Hi Fabio, thanks for the post.  Unfortunately not every shop is going to be entirely Unix, and telling them “don't use Ruby except on Unix” sort of limits the Ruby install base to only Unix systems. It’s like saying “Only use Windows” or “I’m only releasing my app for iOS”.

    Virtualization is a good workaround if you know your target OS is always going to be the same, but it’s not always the answer. If your application will be used by a company where the IT staff is only familiar with Windows, they are going to have to invest in training people to maintain the Unix VM.

    Yes, it’s great if you have one OS that you are targeting and a VM would have solved the problem in this case, but you don’t always have that luxury.

Skip to main content