DevOps Zone is brought to you in partnership with:

After teaching math (and a little computer science) for 14 years, Brian changed careers in 2006 with the idea that getting out of his comfort zone would be good. So now he works as a writer and programmer at Microsoft who specializes in PHP and Windows Azure. Brian is a DZone MVB and is not an employee of DZone and has posted 82 posts at DZone. You can read more from them at their website. View Full User Profile

Bundler and Cross-Platform Development

01.26.2013
| 2082 views |
  • submit to reddit

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"
end

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.

Published at DZone with permission of Brian Swan, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)