Overview
Bundler manages Ruby gem dependencies through declarative specification and deterministic resolution. The core workflow centers on the Gemfile
where developers declare gem requirements, and the Gemfile.lock
file where Bundler records exact versions and dependencies.
The Bundler
module provides programmatic access to dependency management, while the bundle
command-line tool handles installation, updates, and environment setup. Bundler resolves complex dependency graphs, ensuring all gems work together without version conflicts.
# Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 7.0'
gem 'pg', '>= 1.1'
gem 'puma', '~> 5.0'
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
end
Bundler creates isolated environments where applications run with exact gem versions. The Bundler.require
method loads gems according to groups, while Bundler.with_clean_env
provides access to system gems outside the bundle.
# Load gems for current environment
Bundler.require(:default, Rails.env)
# Access bundled gems programmatically
Bundler.load.specs.each do |spec|
puts "#{spec.name}: #{spec.version}"
end
The dependency resolution algorithm examines all gem specifications, calculates compatible versions, and generates a solution that satisfies all constraints. Bundler maintains this solution in Gemfile.lock
, ensuring identical environments across deployments.
Basic Usage
Create a Gemfile
in the project root to declare dependencies. Bundler reads this file during installation and resolution operations.
# Gemfile
source 'https://rubygems.org'
# Exact version
gem 'rake', '13.0.6'
# Version constraints
gem 'minitest', '~> 5.16'
gem 'sqlite3', '>= 1.4', '< 2.0'
# Git repositories
gem 'rack', git: 'https://github.com/rack/rack.git'
# Local path
gem 'custom_gem', path: '../custom_gem'
# Groups for conditional loading
group :development do
gem 'debug'
gem 'rubocop'
end
group :test do
gem 'capybara'
gem 'selenium-webdriver'
end
group :production do
gem 'unicorn'
end
Run bundle install
to resolve dependencies and install gems. Bundler creates Gemfile.lock
with exact versions and dependencies.
# Check current bundle status
bundle check
# Install missing gems
bundle install
# Update specific gems
bundle update rails
# Update all gems
bundle update
Execute commands within the bundle environment using bundle exec
. This ensures the command runs with bundled gem versions rather than system gems.
# Run with bundled gems
bundle exec rake test
bundle exec rails server
bundle exec rspec
# Generate binstubs for common commands
bundle binstubs rake rspec rails
./bin/rake test # Uses bundled version
Load gems programmatically in Ruby applications. The Bundler.require
method loads gems based on current groups.
require 'bundler/setup'
# Load default group
Bundler.require
# Load specific groups
Bundler.require(:default, :development)
# Load for Rails environment
Bundler.require(:default, Rails.env.to_sym)
Configure Bundler behavior through command-line options or configuration files. Settings control installation locations, gem sources, and resolution behavior.
# Configure gem installation path
bundle config set --local path 'vendor/bundle'
# Exclude development gems in production
bundle config set --local without development test
# Use specific number of parallel jobs
bundle config set --local jobs 4
# Check current configuration
bundle config list
Error Handling & Debugging
Bundler raises specific exceptions for different failure scenarios. The Bundler::GemNotFound
exception occurs when gems cannot be located in configured sources.
begin
Bundler.require
rescue Bundler::GemNotFound => e
puts "Missing gem: #{e.name}"
puts "Available in groups: #{e.groups}"
# Attempt recovery or exit gracefully
exit 1
end
Version conflict resolution failures generate Bundler::VersionConflict
exceptions. These include detailed information about conflicting requirements and potential solutions.
begin
Bundler.definition.resolve_remotely!
rescue Bundler::VersionConflict => e
puts "Dependency conflict:"
puts e.message
# Parse conflict details
e.conflicts.each do |conflict|
puts "Gem: #{conflict.name}"
puts "Requirements: #{conflict.requirements.join(', ')}"
end
end
Platform-specific gem installation failures raise Bundler::InstallError
. These often occur with native extensions or missing system dependencies.
# Diagnose installation issues
begin
Bundler::Installer.install(Bundler.root, Bundler.definition)
rescue Bundler::InstallError => e
puts "Installation failed: #{e.message}"
# Check for common issues
if e.message.include?('native extension')
puts "Install system dependencies for native gems"
elsif e.message.include?('permission')
puts "Check file permissions and user access"
end
end
Debug dependency resolution using verbose output and diagnostic commands. The bundle show
command reveals gem locations and versions.
# Show gem information
bundle show rails
bundle show --paths
# List all gems in bundle
bundle list
# Show dependency tree
bundle viz --format=png
# Check for security vulnerabilities
bundle audit check
Lock file corruption or inconsistency triggers Bundler::LockfileError
. Recovery involves regenerating the lock file or resolving conflicts manually.
begin
Bundler.definition.lock
rescue Bundler::LockfileError => e
puts "Lockfile error: #{e.message}"
# Common recovery steps
File.delete('Gemfile.lock') if File.exist?('Gemfile.lock')
system('bundle install')
end
Network connectivity issues during gem fetching raise Bundler::HTTPError
. Implement retry logic and fallback sources for reliability.
retries = 3
begin
Bundler::Source::Rubygems.new.fetch_gem(gem_spec)
rescue Bundler::HTTPError => e
retries -= 1
if retries > 0
sleep(2)
retry
else
puts "Network error: #{e.message}"
raise
end
end
Production Patterns
Deploy applications with exact gem versions using the committed Gemfile.lock
file. Never modify lock files in production environments.
# Production deployment workflow
bundle config set --local deployment true
bundle config set --local without development test
bundle install --frozen
# Verify bundle integrity
bundle check || bundle install
Cache gems during container builds to reduce deployment time and network dependencies. Layer gem installation before application code changes.
# Dockerfile pattern
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true && \
bundle config set --local without development test && \
bundle install
COPY . .
Monitor gem loading performance and memory usage in production applications. Track bundle size and load times across deployments.
# Profile gem loading time
start_time = Time.now
Bundler.require
load_time = Time.now - start_time
puts "Gems loaded in #{load_time.round(3)}s"
# Memory usage before and after
before_memory = `ps -o rss= -p #{Process.pid}`.to_i
Bundler.require
after_memory = `ps -o rss= -p #{Process.pid}`.to_i
puts "Gems added #{after_memory - before_memory} KB memory"
Implement gem vulnerability scanning in deployment pipelines. Use bundle audit
to check for known security issues.
# Security scanning integration
audit_result = system('bundle audit check --update')
unless audit_result
puts "Security vulnerabilities detected"
exit 1
end
# Custom vulnerability checking
vulnerable_gems = []
Bundler.load.specs.each do |spec|
if vulnerable_version?(spec.name, spec.version)
vulnerable_gems << "#{spec.name}-#{spec.version}"
end
end
if vulnerable_gems.any?
puts "Vulnerable gems: #{vulnerable_gems.join(', ')}"
exit 1
end
Handle gem source authentication for private repositories. Configure credentials securely without exposing tokens in version control.
# Configure private gem sources
bundle config set --global credentials.fury.io $GEMFURY_TOKEN
bundle config set --global credentials.github.com $GITHUB_TOKEN
# Gemfile with authenticated sources
source 'https://rubygems.org'
source 'https://gems.fury.io/mycompany/' do
gem 'internal_gem'
end
Optimize bundle installation in CI/CD environments using parallel jobs and caching strategies.
# CI configuration
bundle config set --local jobs 4
bundle config set --local retry 3
bundle config set --local deployment true
# Cache gems between builds
cache_key = Digest::SHA256.hexdigest(File.read('Gemfile.lock'))
cache_path = "cache/bundler/#{cache_key}"
if File.directory?(cache_path)
system("cp -r #{cache_path} vendor/bundle")
else
bundle install
system("mkdir -p cache/bundler && cp -r vendor/bundle #{cache_path}")
end
Performance & Memory
Large dependency graphs impact application startup time and memory consumption. Profile bundle loading to identify expensive gems.
# Measure individual gem load times
gem_times = {}
Bundler.definition.specs.each do |spec|
start_time = Time.now
require spec.name
gem_times[spec.name] = Time.now - start_time
rescue LoadError
# Gem may not have a main require file
end
# Sort by load time
gem_times.sort_by { |_, time| -time }.first(10).each do |name, time|
puts "#{name}: #{(time * 1000).round(2)}ms"
end
Reduce bundle size by excluding unnecessary groups and gems. Use platform-specific gems to avoid installing unused native extensions.
# Gemfile optimization
gem 'pg', platforms: [:ruby]
gem 'sqlite3', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
group :development, optional: true do
gem 'debug'
gem 'rubocop'
end
# Install without optional groups
bundle install --without development test
Memory usage grows with the number of loaded gems and their dependencies. Monitor memory consumption patterns during application lifecycle.
# Track memory usage by gem loading phase
def memory_usage
`ps -o rss= -p #{Process.pid}`.to_i
end
baseline = memory_usage
require 'bundler/setup'
after_setup = memory_usage
Bundler.require
after_require = memory_usage
puts "Bundler setup: #{after_setup - baseline} KB"
puts "Gem loading: #{after_require - after_setup} KB"
puts "Total bundle memory: #{after_require - baseline} KB"
Parallel gem installation reduces resolution and download time. Configure job count based on available CPU cores and network bandwidth.
# Optimize installation performance
require 'etc'
cpu_count = Etc.nprocessors
bundle config set --local jobs [cpu_count, 8].min
# Benchmark installation times
install_times = []
3.times do
FileUtils.rm_rf('vendor/bundle')
start_time = Time.now
system('bundle install --quiet')
install_times << Time.now - start_time
end
average_time = install_times.sum / install_times.length
puts "Average install time: #{average_time.round(2)}s"
Dependency resolution complexity increases exponentially with constraint combinations. Simplify version specifications to improve resolution speed.
# Measure resolution time
resolution_start = Time.now
definition = Bundler::Definition.build('Gemfile', 'Gemfile.lock', nil)
definition.resolve_remotely!
resolution_time = Time.now - resolution_start
puts "Resolution time: #{resolution_time.round(3)}s"
puts "Dependency count: #{definition.dependencies.length}"
puts "Resolved specs: #{definition.specs.length}"
# Identify resolution bottlenecks
definition.dependencies.each do |dep|
if dep.requirements.length > 3
puts "Complex dependency: #{dep.name} (#{dep.requirements.length} constraints)"
end
end
Common Pitfalls
Version constraint syntax confusion leads to unexpected dependency resolution. The ~>
operator allows patch-level updates but restricts minor version changes.
# Common constraint misunderstandings
gem 'rails', '~> 7.0' # Allows 7.0.x, 7.1.x, but not 8.0.x
gem 'rails', '~> 7.0.0' # Allows 7.0.x only, not 7.1.x
gem 'rails', '>= 7.0' # Allows any version >= 7.0, including 8.0, 9.0
# Verify constraint behavior
constraint = Gem::Requirement.new('~> 7.0')
puts constraint.satisfied_by?(Gem::Version.new('7.0.5')) # => true
puts constraint.satisfied_by?(Gem::Version.new('7.1.0')) # => true
puts constraint.satisfied_by?(Gem::Version.new('8.0.0')) # => false
Group specifications affect gem loading in unexpected ways. Gems declared in multiple groups load when any specified group is required.
# Problematic group usage
group :development, :test do
gem 'factory_bot_rails'
end
group :test do
gem 'capybara'
end
# This loads both factory_bot_rails and capybara
Bundler.require(:test)
# Solution: separate group declarations
group :development do
gem 'factory_bot_rails'
end
group :test do
gem 'factory_bot_rails'
gem 'capybara'
end
Lock file modifications without regeneration create inconsistent states. Always use bundle update
commands rather than manual lock file edits.
# Detect lock file inconsistencies
definition = Bundler::Definition.build('Gemfile', 'Gemfile.lock', nil)
if definition.missing_specs.any?
puts "Missing specs: #{definition.missing_specs.map(&:name).join(', ')}"
puts "Run 'bundle install' to resolve"
end
if definition.unlocked_gems.any?
puts "Unlocked gems: #{definition.unlocked_gems.join(', ')}"
puts "Lock file may be inconsistent"
end
Git gem references without specific commits cause deployment inconsistencies. Production deployments may fetch different commits than development environments.
# Problematic git gem usage
gem 'my_gem', git: 'https://github.com/user/my_gem.git'
# Better: specify commit, tag, or branch
gem 'my_gem', git: 'https://github.com/user/my_gem.git',
ref: 'abc123'
gem 'my_gem', git: 'https://github.com/user/my_gem.git',
tag: 'v1.2.3'
gem 'my_gem', git: 'https://github.com/user/my_gem.git',
branch: 'stable'
Platform-specific gems create deployment failures when lock files generate on different operating systems. Use bundle lock --add-platform
to include multiple platforms.
# Add platforms to lock file
bundle lock --add-platform ruby
bundle lock --add-platform x86_64-linux
bundle lock --add-platform x86_64-darwin-20
# Verify platform coverage
platforms = []
Bundler.definition.platforms.each do |platform|
platforms << platform.to_s
end
puts "Supported platforms: #{platforms.join(', ')}"
Source priority and gem name conflicts cause resolution to select unexpected versions. Explicit source blocks prevent ambiguous gem resolution.
# Ambiguous source configuration
source 'https://rubygems.org'
source 'https://gems.mycompany.com'
gem 'rails' # Could come from either source
# Clear source specification
source 'https://rubygems.org'
source 'https://gems.mycompany.com' do
gem 'internal_rails_extension'
end
gem 'rails' # Always from rubygems.org
Reference
Core Commands
Command | Options | Description |
---|---|---|
bundle install |
--deployment , --without GROUP |
Install gems according to Gemfile |
bundle update |
GEM_NAME , --all |
Update gems to latest versions |
bundle exec CMD |
Execute command with bundled gems | |
bundle check |
--path PATH |
Verify bundle installation |
bundle show GEM |
--paths |
Display gem information |
bundle list |
--name-only |
List bundled gems |
bundle outdated |
--pre , --source SOURCE |
Show outdated gems |
bundle clean |
--force |
Remove unused gems |
Configuration Options
Setting | Values | Description |
---|---|---|
deployment |
true , false |
Enable deployment mode |
path |
Directory path | Gem installation directory |
without |
Group names | Exclude gem groups |
with |
Group names | Include gem groups |
jobs |
Integer | Parallel installation jobs |
retry |
Integer | Network retry attempts |
timeout |
Integer | Network timeout seconds |
frozen |
true , false |
Prevent lock file changes |
Gemfile Directives
Directive | Parameters | Description |
---|---|---|
source |
URL, &block |
Define gem source |
gem |
name, *requirements, **options |
Declare gem dependency |
group |
*names, &block |
Group conditional gems |
platform |
*names, &block |
Platform-specific gems |
git_source |
name, &block |
Custom git source |
gemspec |
name: nil, path: '.' |
Load gemspec dependencies |
Version Constraint Operators
Operator | Example | Matches |
---|---|---|
= |
= 1.2.3 |
Exactly 1.2.3 |
> |
> 1.2 |
Greater than 1.2 |
>= |
>= 1.2 |
Greater than or equal 1.2 |
< |
< 2.0 |
Less than 2.0 |
<= |
<= 1.9 |
Less than or equal 1.9 |
~> |
~> 1.2 |
Compatible with 1.2 |
!= |
!= 1.3.0 |
Not equal to 1.3.0 |
Exception Classes
Exception | Cause | Recovery |
---|---|---|
Bundler::GemNotFound |
Gem not in sources | Add gem source or check name |
Bundler::VersionConflict |
Incompatible versions | Relax constraints or update gems |
Bundler::InstallError |
Installation failure | Check system dependencies |
Bundler::LockfileError |
Lock file corruption | Delete and regenerate lock file |
Bundler::HTTPError |
Network failure | Retry or check connectivity |
Bundler::PathError |
Invalid path | Verify file system permissions |
Environment Variables
Variable | Purpose | Values |
---|---|---|
BUNDLE_GEMFILE |
Gemfile location | File path |
BUNDLE_PATH |
Installation path | Directory path |
BUNDLE_WITHOUT |
Excluded groups | Group names |
BUNDLE_DEPLOYMENT |
Deployment mode | true , false |
BUNDLE_JOBS |
Parallel jobs | Integer |
BUNDLE_RETRY |
Retry attempts | Integer |