The Ultimate Guide to Gemfile and Gemfile.lock

Ruby on Rails developers are no strangers to Gemfile and Gemfile.lock. These two files are essential for installing Ruby gems, but they can be confusing if one is unfamiliar with how they work. Here we’ll explore what a Gemfile is, what it contains, and how to use it.

To start, let’s create a default Rails 7 application, then we’ll be able to go through each line of the Gemfile and understand what it all means.

Inside the newly created app directory, we’ll find our Gemfile and Gemfile.lock.

# Newly created Gemfile

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.1.0"

gem "rails", "~> 7.0.3", ">= 7.0.3.1"
gem "sprockets-rails"
gem "sqlite3", "~> 1.4"
gem "puma", "~> 5.0"
gem "importmap-rails"
gem "turbo-rails"
gem "stimulus-rails"
gem "jbuilder"
gem "redis", "~> 4.0"
gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
gem "bootsnap", require: false

group :development, :test do
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
end

group :development do
  gem "web-console"
end

Now let’s start with the most basic of questions.

What is a Gemfile?

In the simplest of terms, A Gemfile is a file that lists all the gems an application requires. When we run bundle install, Bundler finds and installs these gems.

For example, in the above Gemfile, gem "rails", "~> 7.0.3", ">= 7.0.3.1" indicates we’re using the Rails gem in our application.

Where do the gems in the Gemfile come from?

Let’s dive into the first two lines of our newly generated Gemfile and understand where these gems come from.

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

The source keyword is called the Global Source. There should be only one global source per Gemfile. In most cases, the source will be “https://rubygems.org” But if we have our own Gemfury account, then the source can be:

source "https://user:[email protected]"

The git_source method lets us use gems from Git repositories. The built-in :github Git source is one example, but we can also create our own Git sources.

Let’s assume we need a popular authentication gem called devise in our application. And we have decided to install the gem from their Github repo. We can do it in the following different ways.

gem "devise", github: "plataformatec/devise"
gem "devise", github: "plataformatec/devise", branch: "4-stable"
gem "devise", github: "plataformatec/devise", tag: "v4.1.0"
gem "devise", github: "plataformatec/devise", ref: "d4bf52bdfd652cc1d87fa5800a04b288a81fd787"
  • The first approach is the most common way to install a gem from github. It finds the latest version and installs it.
  • The second way is installing a specific gem version from a particular branch.
  • The third way is to install a specific gem version from a particular tag.
  • The fourth way is to install a specific version of the gem from a particular commit.

PS: Other than :github, Bundler also provides :gist and :bitbucket as their built-in git_sources. But we can always create our own git_sources.

How does one specify a ruby version in a Gemfile?

ruby "3.1.0"

The above line instructs Bundler to use Ruby version 3.1.0. Bundler will use the default ruby version if we do not specify one. If the running ruby version does not match the one specified in the Gemfile, Bundler will raise an exception.

Your Ruby version is 2.7.6, but your Gemfile specified 3.1.0

If our local version of Ruby is 2.7.6 and the version specified in the Gemfile is 3.1.0, then an exception will be raised. Further, using the :engine and the :engine_versions options, we can specify the ruby engine and the version we want to use.

ruby "3.1.0", engine: "jruby", engine_version: '9.1.1.0'

What are the various configuration options for a gem?

There are plenty of small and large config options that let us drill down specific attributes of a gem.

Let’s look at the Gemfile again.

gem "rails", "~> 7.0.3", ">= 7.0.3.1"
gem "sprockets-rails"
gem "sqlite3", "~> 1.4"
gem "puma", "~> 5.0"
gem "importmap-rails"
gem "turbo-rails"
gem "stimulus-rails"
gem "jbuilder"
gem "redis", "~> 4.0"
gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
gem "bootsnap", require: false

Now, let us assume we need to install the devise gem. One immediately noticeable thing when looking at our Gemfile is that there are multiple ways to include a gem. Most of the gems have different versions and different platforms associated with them. We may notice that there are multiple different types of version specifiers used.

  • = Equal to (default)
  • != Not equal to
  • > Greater than
  • < Less than
  • >= Greater than or equal to
  • <= Less than or equal to
  • ~> Pessimistically greater than or equal to
Equal To Symbol

The simplest way to specify a gem is to use the “equal to” operator.

gem "devise", "= 4.0.0"

The above line of code will only install the devise gem with the version 4.0.0. If we don’t specify the version number, Bundler will simply install the most recently available version of the gem.

Not Equal To Symbol

Let’s assume we are okay with any devise versions except 3.1.1, as it is not supported by our application. We can do that too!

gem "devise", "!= 3.1.1"

The code above will install the latest version of the devise gem that is not equal to 3.1.1.

Greater Than and Less Than Symbols

What if we want to install the latest version of the devise gem that is greater than 3.1.0 and less than 4.1.0?

gem "devise", "> 3.1.0", "< 4.1.0"
Greater Than or Equal To and Less Than or Equal To Symbols

Now if we want to install the latest version of the devise gem that is greater than or equal to 3.1.0 and less than or equal to 4.1.0?

gem "devise", ">= 3.1.0", "<= 4.1.0"
Pessimistically Greater Than or Equal To Symbols

Let’s say we want our application to work with future versions of the devise gem without breaking the codebase. The only way to ensure this will be to install only the versions which will be backward compatible. So this would mean if we have devise 3.1.0 installed in our system, then 3.1.1 has fewer chances of breaking our application rather than directly installing the latest version 4.1.0 which most probably will have breaking changes. To ensure only a limited range of versions are available for a gem, we can use the Pessimistic Version Operator.

gem "devise", "~> 3.1" # same as >= 3.1.0 and < 4.0.0

To be more specific, we can add more digits after the ~ operator.

gem "devise", "~> 3.1.0" # same as ">= 3.1.0", "< 3.2.0"

These version specifiers raise a very important question we all must have had at some point.

Should we add gems without specifying a version?

Adding a gem without a version specifier is considered to be a bad practice. When we add a gem without a version specifier, we are implicitly adding a version specifier of = 0.0.0 to the gem.

gem "devise" # => gem "devise", "= 0.0.0"

This might work for the current application but in the future, when we upgrade the application, we might get an incompatible version of the gem that might break our application. So we should always specify a version specifier to be safe!

What does require: false mean?

If we look at the Gemfile, we will see that we have added a gem called bootsnap with the option require: false.

gem "bootsnap", require: false

This means that we don’t want to require the gem bootsnap when we run bundle install but we do want to install it. Requiring simply means loading all the related gem files when our application boots up.

This is useful when we are installing a gem that is not directly required by the application. We might have many gems that are responsible for checking code quality or measuring metrics. Such gems might not be in use within the application but are useful for developers. Using require: false allows us to load it lazily, only when we need it.

For example, bootsnap is being used inside the config/boot.rb file. So inside the config/boot.rb file, we can require bootsnap only when we need it.

require "bootsnap/setup"

We can find the above code in the config/boot.rb file where we’re requiring bootsnap to be loaded lazily.

How to install gems in specific environments?

From our Gemfile,

group :development, :test do
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
end

group :development do
  gem "web-console"
end

We see there are two groups (:development and :test) added to our Gemfile. This helps us in installing gems only for specific environments. Gems installed in the development group will only be available in the development environment and the same goes for the rest. debug gem is required only in development and test groups. It is not required in the production group. web-console gem is required only in the development group. It is not required in production and test groups.

Now, for example, let’s assume we want to run sqlite3 in our development and test environment, and PostgreSQL in our production environment. To do this, we’ll have to add sqlite3 in our development and test group and PostgreSQL in our production group.

group :development, :test do
  gem "sqlite3", "~> 1.4"
end

group :production do
  gem "pg", "~> 0.18"
end

Please remember that all gems will be installed in the source machine regardless of the environment we run the application in. This means that your production server will still contain gems from both the test and development environment.

Platforms

Platforms are similar to groups in that they are used to group gems together, but they are used to specify the platforms that a gem is available on.

gem "debug", platforms: %i[ mri mingw x64_mingw ]

The above gem is available for platforms that are mri, mingw, and x64_mingw.

The different platforms available are:

  • ruby C Ruby (MRI), Rubinius or TruffleRuby, but NOT Windows
  • mri Same as ruby, but only C Ruby (MRI)
  • mingw Windows 32 bit ‘mingw32’ platform (aka RubyInstaller)
  • x64_mingw Windows 64 bit ‘mingw32’ platform (aka RubyInstaller x64)
  • rbx Rubinius
  • jruby JRuby
  • truffleruby TruffleRuby
  • mswin Windows

What is a Gemfile.lock?

Finally the Gemfile.lock file contains all the information about the gems that are currently installed. This file is created after we run the bundle install command. A Gemfile.lock has a list of the exact versions of the gems required for the application.

Let’s take a look at our generated Gemfile.lock file.

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

PLATFORMS
  x86_64-darwin-20

DEPENDENCIES
  bootsnap
  debug
  importmap-rails
  jbuilder
  puma (~> 5.0)
  rails (~> 7.0.3, >= 7.0.3.1)
  redis (~> 4.0)
  sprockets-rails
  sqlite3 (~> 1.4)
  stimulus-rails
  turbo-rails
  tzinfo-data
  web-console

RUBY VERSION
   ruby 3.1.0p0

BUNDLED WITH
   2.3.14

In the above Gemfile.lock file, we see that the file is divided into four sections.

GEM

This is the section where we can see the actual gems that are installed. This section has two parts:

  • remote This is the location of the source from where the gems are installed. In our case, the remote is “https://rubygems.org/”
  • specs This is the list of gems that are installed. Here we see that the gems that are installed are more than the ones that are listed in the Gemfile. This is because we need to also install the dependent gems required by the gems that are listed in the Gemfile. For example, from the Gemfile.lock file above, we can see that the rails gem is dependent on several other gems. So we need to install those other gems as well.
PLATFORMS

This section has the list of platforms that the gems are available on.

If we run bundle platform on our application, we will see the following output.

Your platform is: x86_64-darwin-20

Your app has gems that work on these platforms:
* x86_64-darwin-20

Your Gemfile specifies a Ruby version requirement:
* ruby 3.1.0p0
DEPENDENCIES

This section has a list of dependencies that are currently installed for the application. These are the gems that are listed in the Gemfile.

RUBY VERSION

This section has the Ruby version that is specified in the Gemfile.

RUBY VERSION
   ruby 3.1.0p0 # Implies our Gemfile is for Ruby 3.1.0p0

BUNDLED WITH

This section has the version of Bundler that was last used to install the required gems.

BUNDLED WITH
   2.3.14 # Implies we are using Bundler 2.3.14

For more information on Gemfile and Bundler, check out the documentation.

We hope you have a good understanding of Gemfile and Gemfile.lock after reading this article!

Need help on your Ruby on Rails or React project?

Join Our Newsletter