skip to content

Bundle Exec & Setup

bundle exec, Bundler.setup and binstubs make a process see only the locked gems; bundle config sets where they install and which groups are skipped. Asked: why does a bare command load the wrong gem?

on this pageshow

explore

questions

6

With Bundler, why can a bare `rake test` load different gem versions than `bundle exec rake test`, and what does `bundle exec` change?

level: juniorimportance: must knowfreq 76%

answer

  1. newest installed versus locked
  2. already activated rake, Gemfile requires another
  3. RUBYOPT gets -r bundler/setup
  4. BUNDLE_GEMFILE and the bundle's bin on PATH
  5. Kernel.load for Ruby-shebang executables

basics

~20 s

A bare rake starts through RubyGems, which activates the newest installed rake, not the one Gemfile.lock pins. bundle exec runs Bundler.setup first, so the command sees only the locked versions and only the bundle's gems.

solid answer

~50 s

Typed bare, `rake` runs the wrapper RubyGems installed, which activates the newest installed rake and resolves every later `require` against all installed gems. Gemfile.lock is never consulted. If the Rakefile then calls `require "bundler/setup"`, Bundler finds rake already active at the wrong version and raises `Gem::LoadError`: "You have already activated rake 13.4.2, but your Gemfile requires rake 13.3.1. Prepending `bundle exec` to your command may solve this." Without that line, the task quietly runs against whatever versions are newest. `bundle exec rake test` sets `BUNDLE_GEMFILE`, puts the bundle's executables first on `PATH` and adds `-rbundler/setup` to `RUBYOPT`, then loads the executable after `Bundler.setup` has activated exactly the locked gems. For an executable with a Ruby shebang it does that in the same process with `Kernel.load`; other commands are started with `Kernel.exec`. Child Ruby processes inherit the same bundle through those variables.

code

bash · 6 lines
bash
$ rake test
You have already activated rake 13.4.2, but your Gemfile requires rake 13.3.1.
Prepending `bundle exec` to your command may solve this.

$ bundle exec rake test   # runs rake 13.3.1 from Gemfile.lock
$ bin/rake test           # same, through a binstub

go deeper

for a junior

Recall that bundle exec runs a command with exactly the gem versions in Gemfile.lock, while a bare command may pick the newest installed versions.

for a middle

Explain the environment bundle exec sets up (BUNDLE_GEMFILE, RUBYOPT with bundler/setup, PATH) and why the already activated error names bundle exec as the fix.

for a senior

Make every entry point load the bundle: binstubs or bundle exec in scripts, CI and process managers, and require bundler/setup early so a wrong launch fails loudly.

for a principal

Set conventions so no process in the fleet runs outside its bundle, weighing binstubs, bundle exec and container images that isolate gems per application.

## Two ways to start the same command A Ruby project with a **Gemfile.lock** pins exact versions: say `rake 13.3.1` and `minitest 6.0.6`. The machine, meanwhile, may have several versions of each installed, because other projects or a `gem install` put them there. Which version a command sees depends on how it is started. ## What a bare `rake` does When a gem with executables is installed, RubyGems writes a small wrapper script for each executable into its `bin` directory. Running `rake` from the shell runs that wrapper. It asks RubyGems to **activate the newest installed rake** and load its executable. From then on, every `require` of a gem activates the newest installed version that fits the already-active gems. The project's lockfile plays no part. Two outcomes are common: 1. **A loud failure.** Many Rakefiles and test helpers start with `require "bundler/setup"`. `Bundler.setup` checks every locked gem against what is already active and raises `Gem::LoadError`: "You have already activated rake 13.4.2, but your Gemfile requires rake 13.3.1. Prepending `bundle exec` to your command may solve this." 2. **A silent mismatch.** Without that line, the task runs against the newest versions on the machine. Tests may pass locally and fail in CI, or the reverse, because the two machines ran different gem versions. ## What `bundle exec` changes `bundle exec rake test` prepares the process before the command runs: | Change | Effect | |---|---| | `BUNDLE_GEMFILE` set to the project's Gemfile | any Bundler call in this process or a child uses the same bundle | | `-rbundler/setup` added to `RUBYOPT` | every Ruby child process runs `Bundler.setup` before its own code | | the bundle's executable directory put first on `PATH` | a nested `rspec` or `rake` resolves to the bundle's version | | `BUNDLE_BIN_PATH` set | code can shell out to the same `bundle` | Then it runs the command. When the target is a Ruby executable with a Ruby shebang, Bundler **loads it in the same process** with `Kernel.load`, after requiring `bundler/setup`; this avoids starting a second Ruby. The `disable_exec_load` setting turns that optimisation off for the rare tool that depends on `$0` or `__FILE__`. Any other command is started with `Kernel.exec`, and the environment variables above carry the bundle into it. `Bundler.setup` is where the guarantee comes from. It activates **exactly the locked version** of each gem in the requested groups, puts their `lib` directories on the load path and makes RubyGems refuse gems outside the bundle: `Kernel#gem` raises `Gem::LoadError` for them, and `Gem.bin_path` resolves executables inside the bundle. A `require` of a gem that is installed on the machine but absent from the Gemfile fails with `LoadError`, which is what you want: the dependency must be declared. ## Failure messages worth recognising - `bundler: command not found: rspec` (exit status 127), followed by "Install missing gem executables with `bundle install`": no `rspec` executable exists on `PATH` at all. - `can't find executable rspec for gem rspec-core. rspec-core is not currently included in the bundle`: the gem is installed on the machine, but the Gemfile does not list it. - `Gem::LoadError ... already activated ...`: something activated a gem before `Bundler.setup` ran; start the command through `bundle exec` or a binstub. - `LoadError: cannot load such file`: under Bundler, the gem is not in the Gemfile, or its group was not set up. ## Why the lockfile is not enough on its own Gemfile.lock is only data. Nothing in Ruby reads it unless Bundler runs inside the process, and Bundler runs only when something loads it: `bundle exec`, a binstub, or an explicit `require "bundler/setup"`. That is the core of the answer an interviewer wants: - **RubyGems alone** resolves by "newest installed that fits", per process, at the moment of each activation. - **Bundler** resolves once, ahead of time, writes the result to the lockfile, and enforces it at startup. - **Mixing them** in one process - RubyGems activating first, Bundler arriving later - is what produces the already-activated error. The same logic explains why a command can behave differently between a developer laptop with many installed versions and a clean CI image with exactly the bundle: the laptop has more candidates for "newest". ## Habits that avoid the problem - Prefix project commands with `bundle exec`, or use binstubs in `bin/` generated by `bundle binstubs`. - Start scripts with `require "bundler/setup"` so a wrong launch fails loudly instead of silently. - Do not "fix" the conflict with `gem uninstall`; other projects may need that version.

  • Why does `bundle exec` usually not start a second Ruby process for `bundle exec rspec`?
    When the target file has a Ruby shebang, Bundler requires `bundler/setup` and then runs the file with `Kernel.load` inside the process that is already running, saving a Ruby startup. Setting `disable_exec_load` makes it use `Kernel.exec` instead, for tools that depend on `$0` or `__FILE__`.
  • A script started with `bundle exec` spawns `ruby worker.rb`. Does the child also see only the bundle's gems?
    Yes. `bundle exec` put `-rbundler/setup` into `RUBYOPT` and set `BUNDLE_GEMFILE`, and the child inherits both, so it runs `Bundler.setup` against the same Gemfile before its own code. That is also why shelling out to a different project from inside a bundle needs `Bundler.with_unbundled_env`.

saying these in an interview costs you the question

  • Running rake without bundle exec still reads Gemfile.lock
  • bundle exec reads only Gemfile.lock and ignores the Gemfile
  • bundle exec only changes PATH, nothing inside Ruby
  • The fix for already activated errors is uninstalling the newer gem
  • Child processes of bundle exec escape the bundle automatically
open as a page

In Ruby with Bundler, what is the difference between `require "bundler/setup"` and `Bundler.require`, and how do group arguments change each?

level: middleimportance: must knowfreq 55%

basics

~20 s

require "bundler/setup" calls Bundler.setup, which puts the locked gems of all installed groups on the load path but loads none. Bundler.require runs setup and then requires every gem of the named groups, :default when called without arguments.

open as a page

With Bundler 4, what does `bundle binstubs rspec-core` generate, and how does running `bin/rspec` compare with `bundle exec rspec`?

level: middleimportance: should knowfreq 38%

basics

~20 s

bundle binstubs rspec-core writes bin/rspec, a small Ruby script that points BUNDLE_GEMFILE at the project, requires bundler/setup and loads rspec's executable, so it runs the locked version like bundle exec. Bundler 4 removed bundle install --binstubs.

open as a page

With Bundler 4, why does `bundle install --without test --path vendor/bundle` fail, and how are those settings expressed with `bundle config`?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Bundler 4 removed install flags that used to be silently remembered; --without and --path raise InvalidOption. Set them with bundle config set --local without test and bundle config set --local path vendor/bundle, or BUNDLE_WITHOUT and BUNDLE_PATH.

open as a page

In Ruby under Bundler, why does shelling out from one bundled process to another project's `bundle exec` load the wrong Gemfile, and how does `Bundler.with_unbundled_env` fix it?

level: seniorimportance: should knowfreq 24%

basics

~10 s

A process running under Bundler exports BUNDLE_GEMFILE and -rbundler/setup in RUBYOPT, and every child inherits them, so the other project's bundle exec reuses the parent's Gemfile. Bundler.with_unbundled_env runs a block with those variables removed.

open as a page

What does Bundler's `bundle outdated` report, and how do `--strict`, `--filter-major` and `--only-explicit` narrow its output?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

bundle outdated lists gems whose newer versions exist, with current, latest and requested versions, and changes no file. --strict shows only versions the Gemfile allows, --filter-major only major jumps, --only-explicit only Gemfile-declared gems. It exits 1 when anything is outdated.

open as a page