How Codescrum runs and scales Rails apps In Docker With Docker Compose

Juan Pablo Amaya

Juan Pablo Amaya

This is a guest post by Juan Pablo Amaya, a product developer at CodeScrum. Codescrum is a software development company led by Jairo Diaz and focuses on creating user centric digital services and products for clients. This post originally appeared on the CodeScrum blog.

In a past post we shared a Rails starter template (rails-template) with a minimal base for building common rails applications. Now we want to show how to run the rails-template inside a Docker local machine and be able to do simple scale and load balancing.

The diagram below shows the architecture of the example. Basically we run one container for the Mongo database, one for the redis store and multiple containers for the Rails application and the Workers (Sidekiq). On top of that is the Nginx load balancer container that acts as a reverse proxy for the rails containers.

Docker and Docker Compose

For our experiments with docker we are using docker-machine, that let us to create a Docker host on our machine through a Virtualbox VM. We are not going to talk about how to configure Docker on a local machine, but is fairly explained in the Get started with Docker Machine and a local VM guide from the Docker page.

Docker Compose is a tool that let us to define and run with just one file a set of docker containers. For our rails-template we are going to define three classes of containers (webappwebserverdatabase) and linking together. Docker compose also allows us to do simple scaling up or down of any container that we have defined.

Docker images

For each of our container definitions we need some docker images:

  • MongoDB database: we are going to use the official mongo 3.0 image from docker-hub.
  • Redis store: we use the official redis image from docker-hub.
  • Nginx proxy: we are going to use the Automated Nginx reverse proxy for docker containers from jwilder. This allows us to update and reload the nginx proxy to add/remove servers to be load balanced when new containers are started/stopped.
  • Rails app: For this we create our own image from the official ruby 2.2.2 image using this Dockerfile:

  • # Dockerfile
    FROM ruby:2.2.2
    # Install dependencies.
    RUN apt-get update -qq && apt-get install -y build-essential libpq-dev
    # Setup app directory.
    RUN mkdir /myapp
    WORKDIR /myapp
    # Copy the Gemfile and Gemfile.lock into the image and install gems before the project is copied to avoid do bundle install every time some project file change.
    COPY Gemfile /myapp/Gemfile
    COPY Gemfile.lock /myapp/Gemfile.lock
    RUN bundle install --without development test doc --jobs=4
    # Everything up to here was cached. This includes the bundle install, unless the Gemfiles changed. Now copy the app into the image.
    ADD . /myapp
    # Expose unicorn port.
    EXPOSE 8080
  • Worker app: For this we are going to use the same image builded from the Rails app.

Docker compose definition

The docker compose file that define the containers is:

# docker-compose.yml

  build: .
  command: bundle exec unicorn -E production -c config/unicorn.rb
    - .:/myapp  links:
    - db
  env_file: .env
    RACK_ENV: production
    RAILS_ENV: production
    VIRTUAL_HOST: rails-template.docker
  image: mongo:3.0
  command: mongod --smallfiles --quiet
  image: jwilder/nginx-proxy:latest
    - "80:80"  volumes:
    - "/var/run/docker.sock:/tmp/docker.sock"redis:
  image: redis
  build: .
  command: bundle exec sidekiq -e production -c 5
  env_file: .env
    RAILS_ENV: production
    - db
    - redis

The docker-compose file defines a webapp service that use the image from theDockerfile of the project. This runs the application through unicorn, and links to the db service. This also sets some environment variables needed by the app, between them one important is the VIRTUAL_HOST environment variable, this is the trick that tells to the proxy service that this container wants to be proxyfied. We use the rails-template.docker as the virtual server name, so we need to add it to the hosts file:

# /etc/hosts
{docker-machine ip} rails-template.docker

The {docker-machine ip} value is taken from the command:

docker-machine ip boot2docker

The db and redis services are self described.

In the proxy service the only thing to emphasize is that the 80 port is exposed to the host machine to allows to check the application in our local machine

The worker service links to the db and redis services.

Running the containers

To be able to run the rails-template in Docker we need to first build or pull the images, we do this with:

docker-compose build

It is going to take some time while it download the images from docker-hub and build our rail image.

Now we can run the containers with the command:

docker-compose up -d
The -d option is to run all containers in the background.

We can check the app in the browser at http://rails-template.docker
The Rails application also is configured to run a sidekiq async process that prints the current time every time a request to the app is made, we can see that working with:

docker-compose logs worker

Scaling the application

Now that we have the application running we can scale the webapp service to 3 replicas and the worker up 2 replicas. We do this with the command:

docker-compose scale webapp=3
docker-compose scale worker=2
Now there are two more webapp containers running in the docker machine and be automatically load balanced by the proxy container. We can check this making many requests and by opening the webapp logs:

while true; do curl http://rails-template.docker; echo -----; sleep 1; done;

In other console

docker-compose logs webapp

docker-compose logs worker

We would be able to see how the requests are taken by each of the webapp containers and fire a worker background process.

The example code could be found in this repository:

Liked this post? Get more like this in your inbox