skip to content

Bundler

Bundler resolves a Gemfile into a Gemfile.lock and runs code against exactly that gem set with bundle exec. Interviewers use it to see whether you can reproduce an app's dependencies on any machine.

on this pageshow

explore

questions

18

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 a Gemfile, what versions does `gem "rack", "~> 3.1"` accept, and how do `"~> 3.1.4"`, `">= 3.1"` and a bare `"3.1.4"` differ?

level: juniorimportance: must knowfreq 72%

basics

~20 s

In a Gemfile, ~> 3.1 is the pessimistic operator: at least 3.1 and below 4.0. ~> 3.1.4 allows 3.1.4 up to below 3.2, >= 3.1 has no upper bound at all, and a bare "3.1.4" means exactly = 3.1.4.

open as a page

With Bundler, what is the difference between `bundle install` and `bundle update` when a committed Gemfile.lock already exists?

level: juniorimportance: must knowfreq 74%

basics

~20 s

bundle install installs exactly the versions Gemfile.lock records and re-resolves only gems whose Gemfile entry changed. bundle update deliberately unlocks the named gems, or every gem with --all, and moves them to the newest versions the Gemfile allows.

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

In a Gemfile, what does putting gems inside `group :test do ... end` declare, and what does `require: false` on a gem change?

level: middleimportance: must knowfreq 58%

basics

~20 s

A group labels gems (ungrouped ones are in :default) so installs and Bundler.setup can include or skip them, yet every group still resolves into one Gemfile.lock. require: false keeps the gem installed and loadable but stops Bundler.require from requiring it automatically.

open as a page

A CI job using Bundler 4 with `BUNDLE_DEPLOYMENT=true` fails at `bundle install`, saying the lockfile can't be updated because frozen mode is set; what happened, and what is the fix?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Frozen mode forbids any change to Gemfile.lock, and the committed lockfile no longer matches the Gemfile, usually because someone edited the Gemfile without committing the regenerated lockfile. Run bundle install on a development machine and commit the updated Gemfile.lock.

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

In a gem's own Gemfile, what does the `gemspec` line do, and why do runtime dependencies belong in the .gemspec, not the Gemfile?

level: middleimportance: should knowfreq 36%

basics

~20 s

gemspec loads the project's .gemspec, adds the gem itself from path "." so its runtime dependencies resolve, and puts its development dependencies in the :development group. Apps installing the released gem see only the gemspec, never its Gemfile.

open as a page

In a Gemfile, how do the `git:`, `github:` and `path:` options source a gem, and what do `branch:`, `tag:` and `ref:` pin?

level: middleimportance: should knowfreq 40%

basics

~20 s

git: clones a repository and builds the gem from its .gemspec, github: "owner/repo" is shorthand for its https URL, and path: uses a local directory in place. branch:, tag: or ref: choose what to check out, and Gemfile.lock pins the resulting commit.

open as a page

In a Gemfile, what does the `ruby` directive enforce, and how does the `platforms:` option on a gem differ from it?

level: middleimportance: should knowfreq 32%

basics

~20 s

The ruby directive declares which Ruby the app needs; Bundler raises RubyVersionMismatch at install or setup when the running Ruby differs, without installing one. platforms: limits one gem to certain Ruby implementations, and other platforms quietly skip it.

open as a page

In a Bundler 4 Gemfile.lock, what do the GEM, PLATFORMS, DEPENDENCIES, CHECKSUMS and BUNDLED WITH sections each record?

level: middleimportance: should knowfreq 42%

basics

~20 s

GEM lists every resolved gem with its exact version; PLATFORMS lists the platforms the resolution covers; DEPENDENCIES repeats the Gemfile's direct requirements; CHECKSUMS stores a sha256 per gem (default on in Bundler 4); BUNDLED WITH names the Bundler version that wrote it.

open as a page

With Bundler, why did `bundle update rack` also change other gems in Gemfile.lock, and what do `--conservative`, `--patch` and `--strict` change?

level: middleimportance: should knowfreq 33%

basics

~20 s

bundle update rack unlocks rack and all of its dependencies, even ones other gems share, so they can move. --conservative unlocks only the named gems; --patch or --minor prefer smaller bumps; --strict forbids going past that level.

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

With Bundler 4, why does a Gemfile with a second top-level `source` line for a private gem server fail, and how should private gems be declared?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Bundler 4 allows one global source; a second top-level source raises a GemfileEvalError, because a gem name found on both servers was ambiguous. Scope private gems with a source block or a source: option and keep credentials in bundle config.

open as a page

Why can a Gemfile.lock generated on an Apple-silicon Mac break a frozen Linux CI install, and what does Bundler's `bundle lock --add-platform` do about it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The lockfile's PLATFORMS lists only arm64-darwin, and frozen mode is not allowed to add the CI machine's platform, so Bundler refuses. bundle lock --add-platform x86_64-linux re-resolves for that platform and records it, without needing a Linux machine.

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

What does Bundler 4's `cooldown` setting do during resolution, and why does it never downgrade a version already pinned in Gemfile.lock?

level: seniorimportance: nice to knowfreq 14%

basics

~20 s

cooldown makes Bundler ignore gem versions published fewer than N days ago when it resolves. It governs adopting new versions only, so versions already in Gemfile.lock stay usable, except for gems you explicitly name on bundle update.

open as a page