Managing Gems with Bundler

· 7 min read · Updated March 7, 2026 · beginner
bundler gems ruby dependencies

Bundler is the standard dependency manager for Ruby. It ensures your project uses the exact gem versions you specify, preventing the dreaded “it works on my machine” problem that plagues Ruby projects. Whether you’re building a Rails application or a simple Ruby script, understanding Bundler will save you from dependency headaches.

Why Bundler Matters

Before Bundler existed, Ruby developers struggled with gem version conflicts. Your project might need version 2.0 of a library, while another project on the same machine required version 1.0. RubyGems alone couldn’t resolve these conflicts, and gem updates would often break existing applications.

Bundler solves this by locking your project to specific gem versions. It reads your project’s dependencies, resolves conflicts between different gems’ requirements, and installs exactly what you need. More importantly, it records exactly what was installed so anyone else working on your project gets the same versions.

Every Ruby project should use Bundler. It handles transitive dependencies (gems that your gems depend on), ensures reproducible builds, and integrates seamlessly with Rails, Sinatra, and standalone Ruby projects alike.

The Gemfile

The Gemfile lives in your project’s root directory and declares which gems your application needs. This is your source of truth for dependencies.

A basic Gemfile starts with specifying the Ruby version required, then lists your gems:

source "https://rubygems.org"

ruby "3.2.0"

gem "rails", "~> 7.1"
gem "puma", "~> 6.0"
gem "sqlite3", "~> 1.6"

The source directive tells Bundler where to fetch gems. The ruby directive ensures anyone running your project uses a compatible Ruby version.

Version Specifiers

You control which versions Bundler installs using version specifiers:

  • Exact version: gem "nokogiri", "1.15.0" — installs only this version
  • Pessimistic: gem "rake", "~> 13.0" — allows 13.x but not 14.0 (the last number can increase)
  • Greater than: gem "json", ">= 2.0" — any version 2.0 or newer
  • Range: gem "ffaker", ">= 2.1", "< 3.0" — any version from 2.1 up to (but not including) 3.0
  • No version: gem "debug" — gets the latest version that works with other dependencies

The pessimistic operator ~> is the most common. ~> 7.1 means “7.1.0 or newer in the 7.x series, but not 8.0.” This gives you bug fixes while protecting against breaking changes.

Groups and Environments

You can group gems for different environments:

group :development, :test do
  gem "rspec", "~> 3.12"
  gem "factory_bot", "~> 6.2"
end

group :test do
  gem "faker"
end

group :development do
  gem "web-console"
end

Skip loading certain groups in production:

Bundler.setup(:default, :development) unless Rails.env.production?

Conditional Dependencies

Install gems based on the platform or Ruby version:

gem "google-protobuf", platforms: [:ruby]

platforms :ruby do
  gem "sqlite3"
end

gem "rb-inotify", platforms: :jruby

Installing Gems with bundle install

Run bundle install to install all gems specified in your Gemfile. Bundler reads the file, resolves dependencies, and installs everything needed.

bundle install

This creates a Gemfile.lock file that pins every gem to exact versions. Commit this file to version control so your team and deployment environments use identical dependencies.

Common Installation Options

Install without the development group:

bundle install --without development test

The --without flag remembers your preference for future runs. To reset:

bundle install --with development test

Install gems in a specific path for offline or CI environments:

bundle install --deployment

This installs to vendor/bundle and requires all dependencies to be resolved upfront.

Installation Flow

When you run bundle install, Bundler:

  1. Reads your Gemfile
  2. Resolves dependencies — finds compatible versions for everything
  3. Downloads gems from RubyGems.org (or your configured source)
  4. Writes Gemfile.lock with exact versions
  5. Installs gems to your system

If dependency conflicts exist, Bundler aborts with an error explaining the issue.

Understanding Gemfile.lock

The Gemfile.lock is Bundler’s most important artifact. It contains the exact versions of every gem installed, including transitive dependencies.

Here’s what a Gemfile.lock looks like:

GEM
  remote: https://rubygems.org/
  specs:
    rails (7.1.3)
      actioncable (= 7.1.3)
      actionmailer (= 7.1.3)
      actionpack (= 7.1.3)
      actionview (= 7.1.3)
      activejob (= 7.1.3)
      activemodel (= 7.1.3)
      activerecord (= 7.1.3)
      activestorage (= 7.1.3)
      activesupport (= 7.1.3)
      bundler (>= 1.15.0)
      railties (= 7.1.3)

    actioncable (7.1.3)
      actionpack (= 7.1.3)
      activesupport (= 7.1.3)
      nio4r (~> 2.0)
      websocket-driver (>= 0.6.1)
      zeitwerk (~> 2.3)

    nio4r (2.5.9)

PLATFORMS
  ruby
  x86_64-linux

DEPENDENCIES
  rails (~> 7.1)
  puma (~> 6.0)

RUBY VERSION
   ruby 3.2.0p0

BUNDLED WITH
   2.4.21

The GEM section lists every gem and its dependencies. The PLATFORMS section specifies which platforms your bundle works on. DEPENDENCIES mirrors your Gemfile but with resolved versions. RUBY VERSION records what Ruby was used. BUNDLED WITH shows the Bundler version.

Why Gemfile.lock Matters

Gemfile.lock guarantees reproducibility. When your teammate runs bundle install on a different machine, they get identical gem versions. Your CI pipeline gets the same versions as production. No surprise failures from updated dependencies.

Never edit Gemfile.lock manually. Let Bundler manage it.

The exception: version control. Always commit Gemfile.lock. Exclude it only if you intentionally want different environments to resolve their own versions (rare and usually a bad idea).

Common Bundle Commands

Beyond install and update, you’ll use several other bundle commands regularly.

bundle exec

Run any Ruby command within your project’s bundle context:

bundle exec rake db:migrate
bundle exec rails console
bundle exec ruby script.rb

This ensures your command uses gems from your Gemfile, not system gems. Without bundle exec, Ruby might load a different version of a gem, causing confusing errors.

For commands that ship with gems (like rake, rspec, rails), always prepend bundle exec:

# Without bundle exec - may use wrong gem version
rake db:migrate

# With bundle exec - uses exact version from Gemfile.lock
bundle exec rake db:migrate

bundle update

Update gems to newer versions that satisfy your Gemfile constraints:

bundle update
bundle update rails
bundle update nokogiri --conservative

Updating a single gem (bundle update rails) updates only that gem and its dependents. The --conservative flag minimizes changes by updating only to the next compatible version.

Be careful: bundle update can change multiple gems. Always review Gemfile.lock changes before committing.

bundle check

Verify if all dependencies are satisfied:

bundle check

Returns exit code 0 if the bundle is complete, non-zero if gems are missing. Useful in CI pipelines:

bundle check || bundle install

bundle list

Show all gems in the current bundle:

bundle list

Includes gem path information for debugging.

bundle outdated

Check for newer versions than what’s in Gemfile.lock:

bundle outdated
bundle outdated --major

Shows which gems have updates available. Useful before running bundle update.

Gem Binaries

Instead of using bundle exec constantly, add gems’ bin directories to your PATH:

bundle binstubs rspec

This creates executable stubs in bin/rspec that automatically run with the correct bundle context. Add bin/ to your PATH or run commands as ./bin/rspec.

Best Practices

Follow these patterns to avoid common pitfalls.

Always Commit Gemfile.lock

Treat Gemfile.lock as essential as your source code. It ensures every environment uses identical dependencies. The only exception is when you intentionally want to test updated dependencies in a controlled way.

Specify Ruby Version

Include the ruby directive in your Gemfile:

ruby "~> 3.2.0"

This fails fast if someone tries to run your project with an incompatible Ruby version.

Use Pessimistic Version Constraints

Prefer ~> over exact versions or loose constraints:

# Good - gets bug fixes, won't break on major changes
gem "sidekiq", "~> 7.1"

# Risky - could pull incompatible versions
gem "sidekiq", ">= 7.1"

# Too restrictive - no updates without manual change
gem "sidekiq", "7.1.0"

Run bundle install After Gemfile Changes

Never manually edit Gemfile.lock. After changing Gemfile, run bundle install to regenerate the lock file.

Use Groups for Environment-Specific Gems

Keep development and test dependencies separate from production:

group :development, :test do
  gem "rspec-rails"
end

Skip them in production:

bundle install --without development test

Keep Gems Updated

Outdated gems miss security patches. Run bundle outdated periodically and update intentionally:

bundle update --conservative

Review changes, test thoroughly, then commit.

Use bundle exec Consistently

Make it a habit. CI pipelines should always use bundle exec. Don’t rely on PATH manipulation that might load system gems.


Bundler is essential to Ruby development. Master these fundamentals — Gemfile syntax, installation, Gemfile.lock management, and the core commands — and you’ll avoid the dependency problems that frustrate many developers. Start with constrained versions in your Gemfile, always commit Gemfile.lock, and use bundle exec for running any gem command.