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"
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.
Comments
- Anonymous
January 25, 2013
The comment has been removed - Anonymous
January 29, 2013
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.