Bundler Best Practices
Bundler is a great tool to have in the Ruby toolbox, but it's also a bit mysterious to some developers. "Oh cool, I put my gems in this file, bundle install, and that's it. Wait, what's this Gemfile.lock thing? Should that go in my repo? What's the difference between bundle install and bundle update? How do I install my gems when I deploy? Where are my pants?" Let's take a tour of Bundler and find the answers to some of these questions.
Check-in your Gemfile.lock
You've just created a shiny new Gemfile and specified your app's gems. You run bundle install, and Bundler creates a file named `Gemfile.lock`. What is that for?
According to the Bundler website,
Bundler manages an application's dependencies through its entire life across many machines systematically and repeatably.
Gemfile handles the "manages an application's dependencies" part and
Gemfile.lock handles the "systematically and repeatably" part.
Here's what happens: when you run
bundle install, Bundler installs those gems and records the version number for each one in
Gemfile.lock. Now, Bundler has a record of the gem version of each dependency it is managing, and it can now check the gem store against this record when running future commands. More importantly, if you check out someone else's project and it has a
Gemfile.lock file, Bundler will install the exact same gems the other developer is using.
Should you keep your
Gemfile.lock in version control? Yes! It's a critical part of getting the most benefit from Bundler. By doing so, you guarantee that every developer working on the project and every deployment of the app will use exactly the same third-party code. How awesome is that?
Steve Loveless mentioned in the comments this post by Yehuda Katz that says you should not check-in your
Gemfile.lock if you are using Bundler while developing a gem. The basic idea is: when developing an app, you want to lock all of your dependencies down so they cannot change, to ensure that if a dependency releases an incompatible update, your app is insulated. However, when developing a gem, you want to be aware of those incompatibilities, so you can resolve them.
Also, when developing a gem, you should specify dependencies in your gem's
Gemfile should look like this:
source 'http://rubygems.org' gemspec
This tells Bundler to use the
.gemspec file when managing dependencies.
Thanks for the tip, Steve!
Install vs. Update
The most common question I've heard about Bundler is about the difference between
bundle install and
In a nutshell:
bundle install handles changes to the
bundle update upgrades gems that are already managed by Bundler.
Here's an example. Suppose your
Gemfile looks like this:
gem 'httparty' gem 'mocha'
When you run
bundle install, httparty, mocha, and their respective dependencies will be installed.
Now, suppose you need to use an older version of mocha. You update the
Gemfile and specify the version:
gem 'httparty' gem 'mocha', '0.9.9'
bundle install will detect the updated
Gemfile, install version 0.9.9 of mocha, and update the
Now, let's say you decide not to use mocha at all. Remove the dependency from the
bundle install, and Bundler will remove mocha from the
(Note that Bundler will install gems but it will not uninstall them. When you remove a dependency, you must uninstall the gems yourself.)
All of that was done with
bundle install; so what does
bundle update do? Let's say that when we first installed httparty, the latest version was 0.7.0. Now, the latest version is 0.7.4, and we'd like to upgrade our app to use that. Run
bundle update httparty and Bundler will upgrade httparty to 0.7.4 and update the
(Note that running
bundle update with no arguments will upgrade all of your app's gems, which is almost certainly not what you want to do.)
Here are the rules:
- Always use
- If you need to upgrade a dependency that Bundler is already managing, use
bundle update <gem>.
- Don't run
bundle updateunless you want all of your gems to be upgraded.
Deployment the Bundler way
When it's time to deploy, it's a good idea to install gems as part of your app, separate from the gems in the global gem store. This keeps your app isolated from changes to system gems and from other apps on the same box. This can be done with RVM and gemsets, but that is a lot of machinery to configure and maintain.
Bundler provides a simple way to do this, and with no work required: by running
bundle install --deployment, Bundler will install gems into
vendor/bundle instead of using the global gem store.
Better still, if you deploy with Capistrano, add
require 'bundler/capistrano' to your
Capfile and Bundler will automatically do this for you whenever you run
One thing to remember: if you run an executable from one of your gems as part of your deploy, use
bundle exec (e.g.
bundle exec jammit) to ensure that the executable from the bundled gem is used.
Bonus tip! Using the :path option
Here's a bonus tip, because I'm nice like that. Sometimes, you need to use a gem that isn't published anymore, or that has a bug, or that needs a modification specific to your app. How should you add it as a dependency?
One easy way to do this is to fork it on GitHub, make the changes, and use the forked repo's URL in the
:git option when you specify the gem in the
Gemfile. But maybe you don't want to keep that repo around for the lifetime of the app, or the source isn't on GitHub, or you don't want to publish your app-specific changes. In this case, a better solution would be to package the gem as part of your app.
Turns out, there's a simple way to do just that: the
gem method takes a
:path option, which is a path to the directory on the local file system containing the gem. Simply add the gem source to your project (I like to put it in
vendor/gems/<name>) and then in your Gemfile, specify the path:
gem 'mocha', :path => File.join(File.dirname(__FILE__), 'vendor', 'gems', 'mocha')
Now the project comes packaged with the modified gem, instead of relying on an outside resource. Neat!
~ * ~
What are your favorite Bundler tips? Share them in the comments below.