In a past post we shared a Rails starter template (rails-template) with the minimal bases 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.
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 (webapp, webserver, database) and linking together. Docker compose also allows us to do simple scaling up or down of any container that we have defined.
For each of our container definitions we need some docker images:
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
The docker compose file that define the containers is:
# docker-compose.yml
webapp:
build: .
command: bundle exec unicorn -E production -c config/unicorn.rb
volumes:
- .:/myapp
links:
- db
env_file: .env
environment:
RACK_ENV: production
RAILS_ENV: production
VIRTUAL_HOST: rails-template.docker
db:
image: mongo:3.0
command: mongod --smallfiles --quiet
proxy:
image: jwilder/nginx-proxy:latest
ports:
- "80:80"
volumes:
- "/var/run/docker.sock:/tmp/docker.sock"
redis:
image: redis
worker:
build: .
command: bundle exec sidekiq -e production -c 5
env_file: .env
environment:
RAILS_ENV: production
links:
- db
- redis
The docker-compose file defines a webapp service that use the image from the Dockerfile 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.
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
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: http://github.com/codescrum/rails-template-docker-compose-example-1