Docker powered Rails

In a previous post, we saw how we automatically update a Rails application by storing the version key in Amazon DynamoDB.

In this post, I will show how we can integrate this auto-update mechanism in a Docker container on top of a CoreOS server.

ruby-docker

There is a lot of information available on the web about Docker and CoreOS, if you are not familiar with these technologies.

The Rails Container

The Rails Docker container is a custom container built from the well-known Phusion Passenger image. We just added some monitoring tools and the startup script (see below).

When the server starts, we pass data to it (through a Cloud Init user-data specification) that schedules the start of the container.

Here is an extract of the script responsible of the start of the Rails container:

- name: rails.service
  command: start
  enable: true
  content: |
    [Unit]
    Description=Bimeio Rails
    After=docker.service

    [Service]
    TimeoutStartSec=0
    ExecStartPre=/bin/sh -c "/usr/bin/docker inspect bime/rails > /dev/null && /usr/bin/docker rm -f rails || true"
    ExecStartPre=/bin/sh -c "rm -f /home/core/rails || true"
    ExecStart=/bin/sh -l -c "/usr/bin/docker run --name rails --restart=always -p 80:80 -e RAILS_ENV=production bime/rails /sbin/my_init"
    ExecStop=/usr/bin/docker stop rails

So what does this script do ?

This script declares a new service to Systemd named rails and describes how to start and stop the Rails container. It also ensures that the service is started after the docker service necessary in order to create a new container.

When the rails service is started, we have a full docker container based on our image up and running on the server. Now let’s see how Rails is started.

The Update Script

In the init process of the container, there is a custom script called startup.sh. Let’s have a look at the script:

 #!/bin/bash
 cd /home/app/bimeio
 timestamp=`date +%s`
 mkdir -p /opt/releases/$timestamp
 cd /opt/releases/$timestamp
 export AWS_SECRET_ACCESS_KEY=...
 export AWS_ACCESS_KEY_ID=...
 aws s3 cp s3://.../rails.zip .
 unzip rails.zip
 rm rails.zip
 bundle install
 bundle exec rake db:migrate RAILS_ENV=production
 bundle exec whenever -w
 mkdir tmp
 cd /home/app
 rm -f bimeio
 ln -s /opt/releases/$timestamp bimeio
 chown app:app bimeio
 chown -R app:app /opt/releases/$timestamp
 touch bimeio/tmp/restart.txt

The flow

Basically, we create a new folder in the release folder that will contain the new Rails code. Then we retrieve the application source code, install the gems, run migrations and update the symbolic link to the new release. Touching restart.txt notifies Passenger to reload the app based on the new code.

This script is the same as the one used by the auto-update mechanism, but is executed for the first time when the container starts.

Auto-update mechanism

Did you notice the bundle exec whenever -w instruction? This will install the cron jobs in our container. We use the whenever gem to install cron jobs based on a Ruby syntax:

 job_type :rails_script, "source /etc/container_environment.sh && cd /home/app/bimeio && bin/rails runner -e :environment ':task' :output"

 every 1.minutes do
   rails_script 'RailsUpdater.check_update'
 end

As soon as the cron job is installed, the RailsUpdater will check for a new version every minute.

Conclusion

With a solid auto-update mechanism and a range of different technologies, you can leverage an application based on Ruby on Rails and Docker that can be deployed almost anywhere* and automatically kept up to date.

As a SaaS provider, this technique allows us to choose our cloud provider wisely (AWS for now). As we are not dependent on the underlying infrastructure, it fits our philosophy for an Agile process from end to end.

← Back to Home

Yannick Chaze
comments powered by Disqus