Building Microservices with Docker and the Rails API gem

Development

Reading Time: 7 minutes

This article was originally published on Medium by Charles Wang, and with their permission, we are sharing it here for Codeship readers.

Why Choose Docker + Rails API ?

At the company that I work for, we have been using Docker to have consistent development environments for all of our engineers. This makes onboarding a new engineer a piece of cake. I’m still particularly new to Docker and want to share my experience just getting a basic Rails API application set up as a microservice. Like most companies that struggle over the long run with a monolithic Rails app, a microservice architecture provides loose coupling, strong cohesion, independent deploys, and much more.

I’ve built quite a few applications where we’ve used Rails as an API, which is a little overkill since you don’t need all the view logic and additional middleware. Instead, we’ll be using the Rails API gem to build out our microservice APIs. The Rails API gem is integrated into the Rails 5 release, which is currently in beta. I’ve explored this option using Docker, but struggled with setting it up, so I’ll be writing about using a Rails 4 setup and adding the rails-api gem as a dependency to the Gemfile.

You may ask yourself why use Rails API as the tech stack for your microservice. I think the answer lies on what works best for your team. There are plenty of talks on the internet about whether or not your team should move towards microservices and what technology to use. There are a ton of other great technologies where using Node.js, Go, Scala, or Java might fit your situation. I’ve spent some time exploring some of these options and I think they are all great, but my team knows Rails the best and we can develop quickly with it. If I were to pick another tech stack that would fit well with my team, I would go with Node.js since a lot of our new apps are being built with it. I think the overall goal is to make sure your services are small enough to be rewritten if needed.

Let’s start with Docker

My biggest recommendation, if this is your first time using Docker, is to go through the tutorial and installation (I’m using a Mac, but there are tutorials for all the platforms). Next, go through the Rails tutorial setup on the Docker website. This will give you the fundamentals to understand how Docker works and setting up a container to run a Rails app with Postgres.

The Setup

This tutorial is for Mac users, but you can adopt it for a Linux distribution or figure out the equivalent on a Windows platform. First create the directory for your project within the terminal. I’ll be making a project called “inventory manager”, but you can name your project whatever.

mkdir inventory_manager && cd inventory_manager

Then create a few files within the root directory of the project.

touch Dockerfile docker-compose.yml Gemfile Gemfile.lock

Setting up the Dockerfile

The Dockerfile is everything we need to set up our Docker container’s environment.

FROM ruby:2.3.0
RUN apt-get update -qq && apt-get install -y build-essential libmysqlclient-dev
RUN mkdir /inventory_manager
WORKDIR /inventory_manager
ADD Gemfile /inventory_manager/Gemfile
ADD Gemfile.lock /inventory_manager/Gemfile.lock
RUN bundle install
ADD . /inventory_manager

So what is happening in this file? Here we are creating an image using the latest version of Ruby (2.3.0) at the time of this writing from https://hub.docker.com/. We then run the following apt package manager commands on the container. We’ll be using the MySQL client library for development. We’ll then add the local Gemfile and Gemfile.lock to our container’s filesystem and then install the dependencies. Lastly, we add all the contents of the project directory to the container.

Setting up the docker-compose.yml

We need to setup our docker-compose.yml file so that the Docker Compose tool can orchestrate the communication between our Rails API app and our MySQL container.

db:
 image: mysql:latest
 ports:
   — “3306:3306”
 environment:
   MYSQL_ROOT_PASSWORD: mypassword
web:
 build: .
 command: puma
 ports:
   — “9292:9292”
 links:
   - db
volumes:
  — .:/inventory_manage

This configuration sets us up with a mysql container and also a container called web that builds from the Dockerfile and runs the Puma webserver on port 9292. We then link the Rails app container to the mysql container so that they can communicate to each other.

Setting up the Gemfile

Inside the Gemfile we will add the Rails gem, the Rails API gem, the mysql client adapter, Puma web server, and the Active Model Serializers (this is for your formatted JSON responses).

source 'https://rubygems.org'

gem 'rails', '4.2.5'
gem 'rails-api', '0.4.0'
gem 'mysql2'
gem 'puma'

# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

# Use ActiveModelSerializers to serialize JSON responses
gem 'active_model_serializers', '~> 0.10.0.rc3'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug'
end
 
group :development do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
end

Create the Docker Image

Now it’s time to create the Docker image.

docker-compose build
running the Docker build command will go through your Dockerfile to build the image and then install gems.

running the Docker build command will go through your Dockerfile to build the image and then install gems.

Docker image successfully created

Docker image successfully created

You will need to run this command to rebuild your Docker image anytime you make a change to the Gemfile or the Dockerfile. You can see that an image is created by typing:

docker images

Charles Wang 3

New Call-to-action

Create the Rails API application structure

Next we’ll create the application structure by running the Rails API commands in the Docker image. In our docker-compose.yml we have named “web” as the container to run our commands against.

docker-compose run web rails-api new .
Creating the Rails API application structure

Creating the Rails API application structure

Setting Up the Database

Now we need to configure our database.yml file so we can start writing a migration.

development:
 adapter: mysql2
 encoding: utf8
 reconnect: false
 database: inventory_manager_dev
 pool: 5
 username: root
 password: mypassword
 host: db
test:
 adapter: mysql2
 encoding: utf8
 reconnect: false
 database: inventory_manager_test
 pool: 5
 username: root
 password: mypassword
 host: db

The host is “db”, which is the same name we’ve defined in our docker-compose.yml file. This host’s value must match what you’ve defined as your database container in the docker-compose.yml file.

Lets test to see if our web applications runs

Now that the app structure has been built, let’s run the web server to see if we can see the Rails status page.

docker-compose up web
Runs both our database and web app in their respective containers and then runs the Puma server listening on port 9292

Runs both our database and web app in their respective containers and then runs the Puma server listening on port 9292

My docker-machine ip is running on port 192.168.59.100

My docker-machine ip is running on port 192.168.59.100

Make sure to find your docker ip by running docker-machine ip (for me this is aliased to default).

docker-machine ip default

Next Steps

Now that we’ve got our app running, we can start development by creating data models, controllers, etc.

We can create more microservices in this manner and facilitate communication between apps using JSON over http by exposing different web server ports on the same Docker host. Another form of communicating between services is by utilizing messaging such as RabbitMQ. I’ll most likely be writing another blog post on RabbitMQ and communicating between services and how to get that setup.

I hope this post helps others as this was definitely a learning experience for me and I’m continuing to learn more and more about Docker. Feel free to comment and make any suggestions on improving the setup.

Want to test and deploy your microservices with Codeship Pro? Find out more here.

PS: If you liked this article you can also download it as a PDF eBook here: Breaking up your Monolith into Microservices or watch our re-run of our webinar: An Introduction to Building Your Apps with Microservices.

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles.
Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.



We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

  • danny__garcia

    Thanks a lot for this post charles it was very helpful. What kind of issues did you run into while attempting to setup Rails 5 and Docker?

  • Ivan Kryak

    For simple communication between two services i wrote this gem: https://github.com/sck-v/communications
    It has high level message and handler definitions and very easy to use

  • manuel

    Thanks for this post!!!

  • Ryan Thames

    Thanks for the post. You spelled inventory_manager wrong in your docker-compose.yml file.

  • AP

    Thanks! outstanding tutorial…
    It looks like volumes:
    — .:/inventory_manage
    should be indented one level more and inventory_manage => inventory_manager

  • docker-compose.yml gave error “contains an invalid type, it should be an array”. Solved by changing contents to:
    db:
    image: mysql:latest
    ports:
    [“3306:3306”]
    environment:
    MYSQL_ROOT_PASSWORD: mypassword
    web:
    build: .
    command: puma
    ports:
    [“9292:9292”]
    links:
    – db
    volumes:
    [“.:/inventory_manage”]

  • Once I do this when I go to 192.168.58.100:9292 it never loads. I tried 0.0.0.0:9292 and I also used `docker inspect -f` to find the ip address of the machine which was 172.18.0.3:9292 and that did not work either. How do I figure out how to get to the server HTTP view like your screenshot?