Why Bundle exec? What’s the difference?
As an iOS developer, I write ruby scripts too because of the common practices of CocoaPods and Fastlane. We’re often encouraged to run the ruby scripts with Bundle — but wait, what is that, and what’s the difference? How do I set this up with minimum effort?
This question has wandered in my mind and I just couldn’t easily find the answer. After some survey, I guess now I got mine. Taking notes here in case someone also needs them :-).
Gem
Before we dive into this topic, we’ll have to learn what gem is. A ruby package is called a gem. A gem can be distributed and installed. A gem can have its version and can evolve.
Gem is also the name of the program that manages the gems in your environment. You could have multiple versions of the same gem installed in your environment, but when you invoke the gem, you are only able to access the version decided by your search path.
# You are allowed to install multiple versions of the same gem
gem install cocoapods -v 1.0.0
gem install cocoapods -v 1.12.1
# You can look up which version you're invoking
pod --version
But boss, we’re in some trouble here. There are a variety of projects in the computer, each of which could require different versions of the same gem. That is, project A might need pod
of 1.12.1
while a super legacy project B won’t work without pod
of 1.0.0
.
Bundler
Here comes the hero. Bundler was made as the solution to the gem versions. We maintain a Gemfile for each project, where we specify what version of a gem is needed.
# Gemfile in project A
gem 'cocoapods-check', '1.12.1'
Before we install the specified gems, we’ll have to install bundle
the tool:
gem install bundler
From now on, we don’t manage the gems (in this project) with gem
anymore. We use bundle
. To install the required gems listed in Gemfile:
# To install the gems in Gemfile
bundle install
And to execute the gems specified by Gemfile:
# To execute the gems in Gemfile
bundle exec pod --version
You might now wonder the difference from calling pod --version
directly. Only by calling the gem with bundle exec
will we call the specified version in Gemfile. That is, calling bundle exec
in different projects may result in different programs.

Here is a graph that hopefully illustrates what’s going on:
- Gem versions required by Gemfile can be resolved by Bundler.
- Without Bundler, OS just looks for the first version in the env path.
Until now, everything works great! 🎉 You are able to create different Gemfile’s for project A and project B without worrying about the colliding gem versions!
We just finished the basics of Bundler, but there are some insights here, which I view as the most valuable part during my exploration.
Bundler Installs Gems Globally
It’s important to note that Bundler installs gems globally by default. Unlike some package managers, Bundler doesn’t achieve version isolation by installing gems locally within the project. Instead, it works with globally installed gems of various versions, selecting the appropriate version specified in the Gemfile. Different projects asking for the same version of a gem share the same one.
Bundler won’t work without Gemfile
Even if the gem has been globally installed, you are not allowed to run it if the gem isn’t specified in the Gemfile. For example, in a project wherepod
doesn’t appear in its Gemfile, running bundle exec pod --version
leads to the failed to load command
error.
The ‘vendors’ Directory Isn’t Always Necessary
Bundler offers a configurable option called BUNDLE_PATH
, allowing you to change the location where gems are installed. This means the flexibility for you to prevent Bundler from installing gems globally. You can even set up separate .bundle/config
files for different projects. Such projects may assign a directory named ‘vendors’ to host gems locally in the project directory, where ‘vendors’ could be added to .gitignore
because it can be regenerated easily as long as you have BUNDLE_PATH
set to it. However, there’s often no need because Bundler’s default behavior of keeping gems global has several advantages including shared gems among projects, reduced disk space usage, and cleaner project directories. An exception, though, is CI, where the ‘vendors’ has to be created locally for CI to cache and share across runs; that’s another story.
Reference: https://stackoverflow.com/a/32940896/4844397

If this article did any benefit to you, please kindly give a clap so I know I’m not the only one who suffered! Or if you have different opinions, also please leave a comment to let me know! :-)