Guides

Getting Started

Guides

Dependency Management

Gem Development

Publishing & Security

Integrations

Hosting & Sources

Extending

Troubleshooting

Concepts

Reference

Appendix

How dependency resolution works

How Bundler picks one set of gem versions that satisfies your Gemfile and every gemspec at once.

Before installing anything, bundle install has to decide which version of every gem to use. That decision is dependency resolution. Knowing what the resolver is trying to do makes version constraints, Gemfile.lock, and conflict errors much easier to reason about.

The resolution problem

Your Gemfile declares the gems your application uses directly. Each of those gems declares its own dependencies in its gemspec, those dependencies declare more, and so on. See Gemfile and gemspec for how the two files relate. The result is a graph in which the same gem often appears several times with different version requirements. Resolving means choosing exactly one version of every gem in the graph so that all requirements hold at the same time.

Simply taking the newest version of everything does not work. Suppose your Gemfile lists payments and reporting, the newest payments release requires money >= 7.0, and every version of reporting requires money ~> 6.1. No single money satisfies both, so newest-of-everything fails. A valid answer still exists. The resolver can pick an older payments release that accepts money 6.x. Choosing a version for one gem narrows the choices for its dependencies, and those choices narrow the next gem’s, so resolution is a search across combinations rather than a per-gem lookup.

gem install performs the same kind of resolution for a single gem and its dependencies. Bundler resolves the whole application at once and records the answer in Gemfile.lock. Without that record, two machines installing the same list of gems at different times can resolve to different versions, which is the problem Bundler was created to solve.

What version constraints mean

A requirement is one or more comparisons against a version:

gem "rack", ">= 2.2"            # 2.2.0 or later
gem "rack", ">= 2.2", "< 4.0"   # within a range
gem "rack", "= 3.2.6"           # exactly this version

The pessimistic operator ~> allows the last given digit to grow but not the ones before it. ~> 3.2 means >= 3.2 and < 4.0. ~> 3.2.6 means >= 3.2.6 and < 3.3.0. The extra digit matters. ~> 2.2 allows 2.9.9, while ~> 2.2.0 stops within the 2.2.x series and rejects 2.3.0. Pick the level of change you are prepared to absorb automatically.

Prerelease versions contain a letter, like 8.1.0.beta1, and sort before the release they lead up to. The resolver never considers them unless a requirement explicitly names one, so gem "rails", ">= 8.1.0.beta1" opts in and plain gem "rails" does not.

How the resolver works

Both Bundler and gem install resolve with PubGrub, a version solving algorithm originally developed for Dart’s package manager and since adopted across ecosystems. PubGrub explores candidate versions, learns from each dead end which combinations can never work, and uses that knowledge to skip whole regions of the search space. This keeps resolution fast even for large graphs, and when no solution exists it can explain why rather than just giving up.

That explanation is what a conflict error shows. Bundler walks through the conflicting requirement chains, each step naming which gem demanded which versions of which dependency. How to read those chains and work out of a conflict is covered in Troubleshooting common issues.

Resolution and the lockfile

Resolution does not happen on every install. The first bundle install resolves and writes the chosen versions to Gemfile.lock. From then on bundle install reuses the locked versions exactly, which is why it is fast and gives every machine and deploy the same gems. If you edit the Gemfile, the next bundle install re-resolves only as much as the change requires and leaves the rest of the lockfile untouched.

Re-resolving on purpose is what bundle update is for:

bundle update                     # everything, to the newest allowed versions
bundle update rack                # one gem, allowing its dependencies to move
bundle update rack --conservative # one gem, keeping its dependencies locked

Cooldown

A cooldown excludes gem versions published less than a chosen number of days ago from resolution. This narrows the candidate set, so the resolver may pick an older version than it otherwise would, and a resolution can even fail although a compatible version exists, because that version is still inside the window. Error messages note when candidates were excluded by the cooldown, and versions already in your lockfile are never retracted by it.