Bundler groups and platforms

The Gemfile

Bundler reads one file and writes one file: the Gemfile is what the project asks for, the Gemfile.lock is what it got. The Gemfile has groups, and a group is what a command can leave out:

source "https://rubygems.org" gem "rake", "~> 13.0" group :development do gem "csv", "~> 3.3" end

~> 13.0 allows 13.x and not 14.0; that is the pessimistic operator, and it is the useful default for an application. A gem that must not move at all is pinned with = 13.4.2, which is what the lock file does implicitly.

bundle install

The first install resolves the whole tree, fetches what is missing and writes the lock:

bundle install

Resolving dependencies... Fetching csv 3.3.7 Installing csv 3.3.7 Bundle complete! 2 Gemfile dependencies, 2 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed.

The second run resolves nothing and prints "Bundle complete", which is the normal state. A run that fetches every time means the lock is missing or ignored.

Local configuration

bundle config set --local writes a setting into .bundle/config for this project only. The usual one puts the gems inside the project, which makes a CI cache a single folder:

bundle config set --local path vendor/bundle

cat .bundle/config

--- BUNDLE_PATH: "vendor/bundle"

bundle install

Bundle complete! 2 Gemfile dependencies, 2 gems now installed. Bundled gems are installed into `./vendor/bundle`

A production install leaves the development group out: bundle config set --local without development, or bundle install --without development on an older Bundler. The group in the Gemfile and the setting at install time are the two halves of the same decision.

The lock file

The lock records the exact versions, the dependencies and the platforms the resolution ran on. A team commits it; an application deploys it:

tail -14 Gemfile.lock

ruby x86_64-linux-musl DEPENDENCIES csv (~> 3.3) rake (~> 13.0) CHECKSUMS bundler (4.0.23) sha256=407ba8a1d928f412847f819aa5f8b6aef7c59fb51feb517112af8a165487287b csv (3.3.7) sha256=15a3f16f6628da8664d455eb634e7f22031bcf53044923caf56975109e71b9f8 rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 BUNDLED WITH 4.0.23

The platform line is the other half of "works on my machine". A x86_64-linux-musl lock on a laptop that deploys to arm64-darwin needs the second platform added: bundle lock --add-platform arm64-darwin. Without it the deploy resolves again and may pick different natives.

bundle exec

bundle exec runs a command with the locked versions activated. Plain ruby sees every gem installed on the machine; the version under Bundler sees only the lock:

bundle exec ruby -v

ruby 3.4.12 (2026-10-23 revision 366f5fdc00) +PRISM [x86_64-linux-musl]

The difference shows when two projects need different versions of one gem. Running the test suite through bundle exec rake test is the habit that keeps a local pass and a CI pass the same test.

What to watch for

Commit Gemfile.lock for an application and for a gem? An application: yes, always. A library gem: usually not, because the consumer resolves; the gemspec carries the ranges.

Never edit the lock by hand. bundle update rake moves one gem within its range and rewrites the lock; a full bundle update moves everything and deserves its own commit.

Next: Rake in depth

Article author: Arthur Isaev