---
title: "Use Geocoder and MaxMind DB to Geocode IP Addresses"
description: "Geocoding IP Addresses in a Ruby on Rails project using geocoder gem and MaxMind GeoIP database to explore the world of networks."
url: "https://qameta.com/posts/use-geocoder-and-maxmind-db-to-geocode-ip-addresses/"
image: "https://qameta.com/posts/use-geocoder-and-maxmind-db-to-geocode-ip-addresses/ruby-on-rails-geocoder-maxmind-geoip2.png"
---

# Use Geocoder and MaxMind DB to Geocode IP Addresses

![Explore Google IP address data](https://qameta.com/posts/use-geocoder-and-maxmind-db-to-geocode-ip-addresses/ruby-on-rails-geocoder-maxmind-geoip2.png)

Utilizing IP addresses for communication is a fundamental aspect of many web
applications employing HTTP(S) as their protocol, particularly those
interconnected to the internet. However, a multitude of IP addresses are also
generated through protocols such as SSH, SMTP, or IMAP. In this article, we'll
explore how to determine their origins and extract valuable data using the
[geocoder gem](https://github.com/alexreisner/geocoder/) in conjunction with the
[MaxMind GeoLite2
Database](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data).

## Requirements

To proceed with this, it's assumed that you have a [Ruby on
Rails](https://rubyonrails.org) project. It's more advantageous to create
something reusable, and the setup for the [geocoder
gem](https://rubygems.org/gems/geocoder) is simplified.


### Get the GeoLite2 Database from MaxMind

To utilize offline geolocation via the [geocoder
gem](https://rubygems.org/gems/geocoder), you'll need to download the GeoLite2
City database from MaxMind. Simply follow the steps outlined on the developer
page and download the ZIP containing the
[mmdb](https://support.maxmind.com/hc/en-us/articles/4408216157723-Database-Formats)
version of the database. While the country-only version is an option, I suggest
opting for the city database for a richer dataset, making the exploration of IP
addresses worldwide more intriguing.

## Setup your Ruby on Rails Project

The setup process is straightforward within a [Ruby on
Rails](https://rubyonrails.org) project. Let's walk through the steps to
initiate the exploration of the world through IP addresses.

### Add the geocoder gem


To begin, you'll need to add the [geocoder
gem](https://rubygems.org/gems/geocoder)  into your project. We'll utilize it to
search for an IP address and delve into the geographical information surrounding
it. Simply add it to your Gemfile using your preferred text editor:

```diff
# Gemfile

+gem "geocoder"
```

Once you've added the geocoder gem to your Gemfile, proceed to run bundle
install and then restart your Ruby on Rails application or the Rails Console.

```sh
bundle install
```

You can accomplish this in a single step by executing the command `bundle add
<name>` within your Ruby on Rails directory.

```sh
$ bundle add geocoder
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
...
Fetching geocoder 1.8.2
...
Installing geocoder 1.8.2

$ git diff Gemfile
+
+gem "geocoder", "~> 1.8"
```

The [geocoder gem](https://rubygems.org/gems/geocoder) has a Ruby on Rails
integration. Just restart your server and you can start using it.

### Add maxminddb gem for the geocoder gem

Geocoder utilizes the [maxminddb gem](https://rubygems.org/gems/maxminddb) to query an [mmdb](https://support.maxmind.com/hc/en-us/articles/4408216157723-Database-Formats) file for information. Simply include it in your Gemfile as you did for the geocoder gem:

```diff
# Gemfile

+ gem "maxminddb"
```

Run `bundle install` to install the current version of [maxminddb
gem](https://rubygems.org/gems/maxminddb).

Or just run:

```sh
$ bundle add maxminddb
```

Don't forget to restart your Ruby on Rails project to load the newly added gem.

### Create the geocoder configuration file

To enable the [geocoder gem](https://rubygems.org/gems/geocoder) to utilize a
MaxMind database for offline geolocation of IP addresses, we need to configure
it. You can generate a basic configuration file for this purpose by running the
following command in your Ruby on Rails project:

```sh
# generate config/initializers/geocoder.rb
rails generate geocoder:config
```

The generated configuration file provides various options, but we only need to
focus on a few lines of it. I've streamlined it by removing all the optional
setups, resulting in this concise version:

```ruby
Geocoder.configure(
  ip_lookup: :geoip2,
    geoip2: {
    file: "vendor/maxmind/GeoLite2-City.mmdb"
  }
)
```

Since I've predetermined the path for the mmdb file, it should be located within
your Ruby on Rails project at `vendor/maxmind/GeoLite2-City.mmdb`. You have the
option to choose a different location, but you can also simply use the one I've
designated.

## Test the setup using Ruby on Rails Console

To verify the setup, let's begin by testing it in the Ruby on Rails console.
However, for testing purposes, we require an IP address. I've opted to use the
IP address of an old friend, [ugu.com](https://ugu.com), for this test:

```sh
% ping -c 1 ugu.com
PING ugu.com (76.50.19.84): 56 data bytes
```

We will use the IP `76.50.19.84` for the first test:

```sh
% irb
irb(main):001> geo = Geocoder.search("76.50.19.84")
irb(main):002> geo
=>
[#<Geocoder::Result::Geoip2:0x000000013abe6230
  @cache_hit=nil,
  @data=
   #<MaxMindDB::Result:0x000000013abe6a28
    @raw=
     {"city"=>{"geoname_id"=>5405288, "names"=>{"en"=>"Valencia"}},
      "continent"=>
       {"code"=>"NA",
        "geoname_id"=>6255149,
        "names"=>{"de"=>"Nordamerika", "en"=>"North America", "es"=>"Norteamérica", "fr"=>"Amérique du Nord", "ja"=>"北アメリカ", "pt-BR"=>"América do Norte", "ru"=>"Северная Америка", "zh-CN"=>"北美洲"}},
      "country"=>
       {"geoname_id"=>6252001,
        "iso_code"=>"US",
        "names"=>{"de"=>"Vereinigte Staaten", "en"=>"United States", "es"=>"Estados Unidos", "fr"=>"États Unis", "ja"=>"アメリカ", "pt-BR"=>"EUA", "ru"=>"США", "zh-CN"=>"美国"}},
      "location"=>{"accuracy_radius"=>10, "latitude"=>34.4463, "longitude"=>-118.5356, "metro_code"=>803, "time_zone"=>"America/Los_Angeles"},
      "postal"=>{"code"=>"91354"},
      "registered_country"=>
       {"geoname_id"=>6252001,
        "iso_code"=>"US",
        "names"=>{"de"=>"Vereinigte Staaten", "en"=>"United States", "es"=>"Estados Unidos", "fr"=>"États Unis", "ja"=>"アメリカ", "pt-BR"=>"EUA", "ru"=>"США", "zh-CN"=>"美国"}},
      "subdivisions"=>
       [{"geoname_id"=>5332921,
         "iso_code"=>"CA",
         "names"=>{"de"=>"Kalifornien", "en"=>"California", "es"=>"California", "fr"=>"Californie", "ja"=>"カリフォルニア州", "pt-BR"=>"Califórnia", "ru"=>"Калифорния", "zh-CN"=>"加州"}}],
      "network"=>"76.50.18.0/23"}>>]
```

The result of the query, `geo`, is an ruby [Array](https://ruby-doc.org/3.2.2/Array.html). By default, the [geocoder gem](https://rubygems.org/gems/geocoder)  might return multiple locations for a query, which is common for searches such as a city name like "York" (which could result in multiple places like York, New York, Yorkshire, etc.). However, for an IP address, we simply utilize the first result, as there is only one possible location.

We can proceed by selecting the first element in the array:

```ruby
irb(main):002> geo.first
=>
#<Geocoder::Result::Geoip2:0x000000013abe6230
  @cache_hit=nil,
  @data=
   #<MaxMindDB::Result:0x000000013abe6a28
...
```

As
[Geocoder::Result::Geoip2](https://github.com/alexreisner/geocoder/blob/master/lib/geocoder/results/geoip2.rb)
has predefined functions, we can use them to query the data:

```ruby
irb(main):004> geo.first.city
=> "Valencia"
irb(main):005> geo.first.country
=> "United States"
irb(main):006> geo.first.country_code
=> "US"
```

This is highly beneficial for many use cases developers encounter. You can
simply utilize the provided data and construct the application accordingly.
However, MaxMind offers additional features within the database.

```ruby
irb(main):007> data = geo.first.data
=>
{"city"=>{"geoname_id"=>5405288, "names"=>{"en"=>"Valencia"}},
...
irb(main):011> data
=>
{"city"=>{"geoname_id"=>5405288, "names"=>{"en"=>"Valencia"}},
 "continent"=>
  {"code"=>"NA",
   "geoname_id"=>6255149,
   "names"=>
    {"de"=>"Nordamerika",
     "en"=>"North America",
     "es"=>"Norteamérica",
     "fr"=>"Amérique du Nord",
     "ja"=>"北アメリカ",
     "pt-BR"=>"América do Norte",
     "ru"=>"Северная Америка",
     "zh-CN"=>"北美洲"}},
 "country"=>
  {"geoname_id"=>6252001,
   "iso_code"=>"US",
   "names"=>
    {"de"=>"Vereinigte Staaten",
     "en"=>"United States",
     "es"=>"Estados Unidos",
     "fr"=>"États Unis",
     "ja"=>"アメリカ",
     "pt-BR"=>"EUA",
     "ru"=>"США",
     "zh-CN"=>"美国"}},
 "location"=>
  {"accuracy_radius"=>10,
   "latitude"=>34.4463,
   "longitude"=>-118.5356,
   "metro_code"=>803,
   "time_zone"=>"America/Los_Angeles"},
 "postal"=>{"code"=>"91354"},
 "registered_country"=>
  {"geoname_id"=>6252001,
   "iso_code"=>"US",
   "names"=>
    {"de"=>"Vereinigte Staaten",
     "en"=>"United States",
     "es"=>"Estados Unidos",
     "fr"=>"États Unis",
     "ja"=>"アメリカ",
     "pt-BR"=>"EUA",
     "ru"=>"США",
     "zh-CN"=>"美国"}},
 "subdivisions"=>
  [{"geoname_id"=>5332921,
    "iso_code"=>"CA",
    "names"=>
     {"de"=>"Kalifornien",
      "en"=>"California",
      "es"=>"California",
      "fr"=>"Californie",
      "ja"=>"カリフォルニア州",
      "pt-BR"=>"Califórnia",
      "ru"=>"Калифорния",
      "zh-CN"=>"加州"}}],
 "network"=>"76.50.18.0/23"}
```

If your data lacks information regarding the continent to which the country
associated with the IP address belongs, you can query it as follows:

```ruby
irb(main):015> data["continent"]
=>
{"code"=>"NA",
 "geoname_id"=>6255149,
 "names"=>{"de"=>"Nordamerika", "en"=>"North America", "es"=>"Norteamérica", "fr"=>"Amérique du Nord", "ja"=>"北アメリカ", "pt-BR"=>"América do Norte", "ru"=>"Северная Америка", "zh-CN"=>"北美洲"}}
irb(main):016> data["continent"]["names"]["en"]
=> "North America"
```

If you are interested in the networks associated with the IP address, you can
also query them in this manner:

```ruby
irb(main):017> data["network"]
=> "76.50.18.0/23"
```

Since the data object is a Ruby [Hash](https://ruby-doc.org/3.2.2/Hash.html),
we can access any data within it.

### Explore Google DNS Server

To demonstrate that the data may vary, let's examine one of [Google's Public DNS
servers](https://developers.google.com/speed/public-dns) and observe what
MaxMind displays as its location:

```ruby
irb(main):019> google = Geocoder.search("8.8.8.8")
=>
[#<Geocoder::Result::Geoip2:0x000000011925b740
...
irb(main):020> google.first
=>
#<Geocoder::Result::Geoip2:0x000000011925b740
 @cache_hit=nil,
 @data=
  #<MaxMindDB::Result:0x000000011925bb00
   @raw=
    {"continent"=>
      {"code"=>"NA",
       "geoname_id"=>6255149,
       "names"=>{"de"=>"Nordamerika", "en"=>"North America", "es"=>"Norteamérica", "fr"=>"Amérique du Nord", "ja"=>"北アメリカ", "pt-BR"=>"América do Norte", "ru"=>"Северная Америка", "zh-CN"=>"北美洲"}},
     "country"=>
      {"geoname_id"=>6252001,
       "iso_code"=>"US",
       "names"=>{"de"=>"Vereinigte Staaten", "en"=>"United States", "es"=>"Estados Unidos", "fr"=>"États Unis", "ja"=>"アメリカ", "pt-BR"=>"EUA", "ru"=>"США", "zh-CN"=>"美国"}},
     "location"=>{"accuracy_radius"=>1000, "latitude"=>37.751, "longitude"=>-97.822, "time_zone"=>"America/Chicago"},
     "registered_country"=>
      {"geoname_id"=>6252001,
       "iso_code"=>"US",
       "names"=>{"de"=>"Vereinigte Staaten", "en"=>"United States", "es"=>"Estados Unidos", "fr"=>"États Unis", "ja"=>"アメリカ", "pt-BR"=>"EUA", "ru"=>"США", "zh-CN"=>"美国"}},
     "network"=>"8.8.8.0/23"}>>
irb(main):021> google.first.city
=> ""
irb(main):022> google.first.country
=> "United States"
```

As we can see, there are IP addresses, which don't have further information
except the country.

## Summary

MaxMind offers a wealth of intriguing information about IP addresses through
their GeoIP2 Lite database. There are numerous use cases, such as enhancing
security, gaining a deeper understanding of service usage, or simply exploring
the world through IPs.

Depending on your specific use case, I advise validating the data before relying
on it blindly. While it may be sufficiently accurate for one scenario, it could
be inaccurate for another, as this data is built on certain assumptions that may
not align with your needs.
