Table of Contents
- What is Bundler?
- Installation
- Setting Up Bundler in a Project
- The Gemfile: Defining Dependencies
- Gemfile.lock: Ensuring Consistency
- Installing Dependencies
- Updating Dependencies
- Removing Dependencies
- Working with Different Environments
- Advanced Bundler Features
- Troubleshooting Common Issues
- Conclusion
- References
What is Bundler?
Bundler is a Ruby gem that manages your project’s dependencies by:
- Declaring which gems your project needs (via a
Gemfile). - Resolving version conflicts between gems.
- Installing missing gems.
- Locking dependency versions to ensure consistency across environments.
Before Bundler (pre-2010), Rubyists relied on manual gem installation or tools like gemsets, which were error-prone and lacked cross-environment consistency. Bundler solved these problems by centralizing dependency management, making it a cornerstone of modern Ruby development.
Installation
Bundler itself is a Ruby gem, so installation is straightforward.
Prerequisites
- Ruby (2.3.0 or later recommended; Bundler 2.x requires Ruby 2.3+).
- RubyGems (usually included with Ruby).
Install Bundler
Run this command in your terminal:
gem install bundler
To verify installation, check the version:
bundler -v
# Output: Bundler version x.y.z
Note: Some Ruby distributions (e.g., via RVM, rbenv, or system Ruby) include Bundler pre-installed. If you see a version, you’re good to go!
Setting Up Bundler in a Project
To start using Bundler in a project, navigate to your project directory and run:
bundle init
This generates a Gemfile—the heart of Bundler’s workflow. The default Gemfile looks like this:
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# gem "rails"
Now you’re ready to define your dependencies!
The Gemfile: Defining Dependencies
The Gemfile is a human-readable manifest that lists your project’s gems and their requirements. Let’s break down its key components.
Sources
Bundler needs to know where to fetch gems. The default source is RubyGems.org, the official gem repository:
source "https://rubygems.org" # Default: fetch gems from RubyGems
You can add additional sources (e.g., for private gems):
source "https://gems.example.com" # Private gem server
Specifying Gems
To add a gem, use the gem keyword followed by the gem name. For example:
gem "rails" # Core web framework
gem "rspec" # Testing library
gem "nokogiri" # HTML/XML parser
Version Constraints
By default, Bundler installs the latest version of a gem. To avoid unexpected breaking changes, specify version constraints:
| Syntax | Meaning | Example |
|---|---|---|
~> 2.0 | Pessimistic: “up to the next major version” (e.g., 2.0, 2.1, …, 2.9) | gem "rails", "~> 7.0" (7.0.x) |
= 2.1.3 | Exact version | gem "rspec", "= 3.12.0" |
>= 2.0 | Minimum version (no upper limit) | gem "nokogiri", ">= 1.13.0" |
>= 2.0, < 3.0 | Range: between 2.0 (inclusive) and 3.0 (exclusive) | gem "rack", ">= 2.2.0", "< 3.0" |
Groups
Group gems by environment (e.g., development, testing) to avoid installing unnecessary gems in production:
# Development-only tools (e.g., linters, debuggers)
group :development do
gem "rubocop" # Code linter
gem "pry" # Debugger
end
# Test-only tools
group :test do
gem "rspec" # Testing framework
gem "simplecov" # Code coverage
end
# Production-only gems
group :production do
gem "pg" # PostgreSQL adapter (for production DB)
end
Non-Rubygems Sources
Install gems from Git repositories, local paths, or custom URLs:
Git Repositories
For gems hosted on GitHub (or other Git services):
# From GitHub (uses the `git_source` defined earlier)
gem "devise", git: "https://github.com/heartcombo/devise.git"
# Specific branch/tag/commit
gem "devise", git: "https://github.com/heartcombo/devise.git", branch: "main"
gem "devise", git: "https://github.com/heartcombo/devise.git", tag: "v4.9.0"
gem "devise", git: "https://github.com/heartcombo/devise.git", ref: "a1b2c3d" # Commit hash
Local Paths
Test a gem you’re developing locally:
gem "my_gem", path: "../my_gem" # Path to local gem directory
Platforms
Specify gems for specific Ruby implementations (e.g., MRI, JRuby) or operating systems:
# Only install on MRI Ruby (not JRuby)
gem "sqlite3", platforms: :ruby
# Install on Windows
gem "win32console", platforms: :mswin
# Multiple platforms
gem "jdbc-postgres", platforms: [:jruby, :mingw]
Example Gemfile
Here’s a realistic Gemfile with comments:
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Core dependencies (all environments)
gem "rails", "~> 7.0" # Rails 7.0.x
gem "pg", "~> 1.4" # PostgreSQL adapter
gem "redis", ">= 4.0" # Redis client
# Development tools
group :development do
gem "rubocop", "~> 1.30" # Linter
gem "pry" # Debugger
end
# Testing tools
group :test do
gem "rspec-rails", "~> 6.0" # RSpec for Rails
gem "capybara", "~> 3.38" # Integration testing
end
# Local gem (for development)
gem "my_custom_gem", path: "../my_custom_gem", group: :development
# Git-based gem (bleeding-edge feature)
gem "devise", git: "https://github.com/heartcombo/devise.git", branch: "main"
Gemfile.lock: Ensuring Consistency
When you run bundle install, Bundler generates a Gemfile.lock file. This file:
- Records the exact version of every gem installed (including transitive dependencies).
- Locks the dependency tree to ensure everyone working on the project (or deploying it) uses the same gem versions.
Example Snippet of Gemfile.lock
GEM
remote: https://rubygems.org/
specs:
rails (7.0.4)
actioncable (= 7.0.4)
actionmailbox (= 7.0.4)
...
pg (1.4.5)
redis (4.6.0)
PLATFORMS
ruby
DEPENDENCIES
capybara (~> 3.38)
devise!
pg (~> 1.4)
rails (~> 7.0)
redis (>= 4.0)
rspec-rails (~> 6.0)
BUNDLED WITH
2.4.10
Key Notes:
- Commit
Gemfile.lockto version control (Git, SVN, etc.). This ensures consistency across development, testing, and production. BUNDLED WITHshows the Bundler version used to generate the lock file.
Installing Dependencies
Once your Gemfile is configured, install gems with:
bundle install # Shortcut: `bundle`
Basic Installation
bundle install does the following:
- Reads your
Gemfileand resolves dependencies (checks for version conflicts). - Installs missing gems to your system’s gem directory (e.g.,
~/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0). - Updates
Gemfile.lockwith the exact versions installed.
Project-Specific Gems (—path)
To install gems locally in your project (instead of globally), use --path:
bundle install --path vendor/bundle
This creates a vendor/bundle directory with all gems. Use this to avoid polluting your global gem set or conflicting with other projects.
Excluding Environments (—without)
To skip gems in specific groups (e.g., development or test in production):
bundle install --without development test
Bundler saves this preference to .bundle/config, so future bundle install commands will automatically exclude these groups.
Deployment Mode (—deployment)
For production deployments, use --deployment for strict consistency:
bundle install --deployment
This:
- Requires
Gemfile.lock(fails if missing). - Installs gems from
Gemfile.lockonly (no network checks for updates). - Disables gem installation to the global directory (uses
vendor/bundleby default).
Updating Dependencies
Over time, gem maintainers release updates (bug fixes, new features). Use Bundler to update safely.
Checking for Outdated Gems
First, see which gems have newer versions available:
bundle outdated
Example output:
Outdated gems included in the bundle:
* rspec (3.12.0 < 3.13.0)
* nokogiri (1.13.3 < 1.14.0)
Updating Specific Gems
To update a single gem (and its dependencies) to the latest compatible version:
bundle update rspec # Updates rspec and its dependencies
This modifies Gemfile.lock with the new versions.
Updating All Gems
To update all gems to the latest versions allowed by your Gemfile constraints:
bundle update # Updates all gems (use with caution!)
Warning: Updating all gems increases the risk of breaking changes. Always test thoroughly in development first!
Removing Dependencies
To remove a gem:
- Delete the
gem "gemname"line from yourGemfile. - Run
bundle install(Bundler updatesGemfile.lockto remove the gem and its unused dependencies).
To clean up unused gems from your system (e.g., after removing a gem):
bundle clean # Removes gems not in Gemfile.lock
Note: Use
bundle clean --dry-runfirst to preview changes.
Working with Different Environments
Bundler uses groups to separate gems by environment. For example:
Install Gems for Production Only
bundle install --without development test # Skips dev/test groups
Run Commands in a Specific Group
To execute a command using gems from a group (e.g., run tests with rspec):
bundle exec rspec # Ensures `rspec` from the `test` group is used
Advanced Bundler Features
bundle exec
bundle exec ensures you use the project-specific gem versions (from Gemfile.lock) instead of global gems. For example:
bundle exec rails server # Uses the Rails version in your Gemfile
bundle exec rspec spec/ # Uses the RSpec version in your Gemfile
Without bundle exec, you might accidentally use a globally installed gem with a different version.
bundle doctor
Diagnose common issues (e.g., missing gems, outdated Bundler):
bundle doctor
Example output:
The following gems are missing from the current bundle:
* pry (3.1.2)
Bundler version 2.4.10 is out of date. Update with `gem install bundler`.
bundle open
Open a gem’s source code in your default editor (great for debugging):
bundle open rails # Opens the Rails gem directory
bundle cache
Cache gems locally for offline installation (e.g., for deployments with no internet):
bundle cache # Creates `vendor/cache` with gem files (.gem)
Later, install from the cache with:
bundle install --local # Uses cached gems instead of downloading
Troubleshooting Common Issues
1. Dependency Resolution Error
Problem: Bundler can’t find compatible versions of gems (e.g., “conflicting dependencies for gem ‘rack’”).
Solution:
- Check
bundle outdatedto identify conflicting gems. - Loosen version constraints in your
Gemfile(e.g., use~> 2.0instead of= 2.1.3). - Update one of the conflicting gems to a version that resolves the conflict.
2. Gemfile.lock Out of Sync
Problem: “Your Gemfile.lock is corrupt” or “Gemfile changed since Gemfile.lock was generated”.
Solution:
- Run
bundle installto regenerateGemfile.lock. - If that fails, delete
Gemfile.lockand runbundle install(last resort—this may update gem versions).
3. BUNDLED WITH Version Mismatch
Problem: “Bundler could not find compatible versions for gem ‘bundler’“.
Solution:
- Install the Bundler version specified in
Gemfile.lock(e.g.,gem install bundler -v 2.4.10).
4. Permission Errors
Problem: “You don’t have write permissions for the /Library/Ruby/Gems/2.6.0 directory”.
Solution:
- Use
--user-installto install gems to your user directory:gem install bundler --user-install. - Or use a Ruby version manager (rbenv, RVM) to avoid system Ruby permission issues.
Conclusion
Bundler is an indispensable tool for Ruby development, ensuring your project’s dependencies are consistent, reproducible, and easy to manage. By mastering Gemfile syntax, bundle install, bundle update, and bundle exec, you’ll avoid version conflicts and streamline collaboration.
Best Practices:
- Commit
GemfileandGemfile.lockto version control. - Use version constraints to avoid unexpected updates.
- Test gem updates in development before deploying to production.
- Use
bundle execto run project-specific commands.