Standing up Ruby on Rails with Docker and Tutum

tutorial ruby

Bootstrapping a development environment can be overwhelming. What starts out as a project that should take only a few minutes, quickly turns into hours of searching and reading Stack Overflow. It seems that Ruby is especially difficult to get a sane, up to date, environment running. Maybe my system libraries are different than yours, or my environment is slightly different, whatever the case may be, it takes far too long to get going than it should.

In this tutorial we are going to bootstrap a modern Ruby development environment on OS X and create a brand new Ruby on Rails application using only Docker. Using Docker guarantees that your development environments will be the same and best of all: reproducible!

tl;dr

We install boot2docker and docker-compose. Then we will bootstrap a Ruby project and then a Rails app entirely within Docker. After setting up Rails to work with a Postgres database we will deploy a simple scaffold to production using Tutum and bootstrap the database there.

Check out and deploy the completed tutorial on GitHub.

Docker

The first thing that we need is Docker. Docker currently only runs on Linux, so if you are running OS X you will need to run Linux inside a virtual machine, but don’t worry, there is a great tool that can do that with one download. Boot2docker is a single package that will install the following:

Please install Docker now by following one of these instructions. Here are a couple tutorials on installing Docker:

And the official documentation:

  • Mac
  • Ubuntu
  • Many others are available at the above links

NOTE: I’ve installed boot2docker many times and it seems to always work well. If you have any issues with the installation process please see their GitHub project.

Docker Compose

The second and last dependency that we need is Docker Compose. This tool will make it really easy to launch multiple Docker containers that all work together. It will allow us to launch Rails and a database with one command.

Installation from these docs should be fairly simple:

curl -L https://github.com/docker/compose/releases/download/1.2.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

Boot me up Docker

Let’s create our VM that we will be using to run Docker in.

boot2docker init
boot2docker start

Follow the instructions to setup the Docker environment (copy and paste 3 lines of code into your terminal).

Once you are done you should be able to execute docker ps and see this output:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Basic Ruby setup

Now that our prerequisites have been installed, it’s time to get our Rails app bootstrapped.

Create a new directory on your machine that will house our Rails app and cd into it:

mkdir myApp && cd myApp

Now let’s create our Docker environment by creating a new file called Dockerfile, with the following content:

FROM ruby:2.2-onbuild

Finally, we need a Gemfile that will setup our Ruby dependencies:

source 'https://rubygems.org'

gem 'rails'

Bundle it like it’s Twenty Fifteen

Now comes the fun part. If you were developing this Rails app locally the next step would be to runbundle install to download all of the dependent gems. A default install of OS X does include Ruby, but I want to assume that my local version of Ruby is not the same as your version, and that either one of our Rubies could be broken. So let’s use Docker to make sure we are both on the same page and that the environment is guaranteed to work.

Execute the following command to run bundle install from the Docker environment:

docker run -it -v $(pwd):/usr/src/app -w /usr/src/app ruby:2.2 bundle install

What we are doing here is starting up a new container using the ruby:2.2 Docker image. We are setting our local directory, which we find by executing a sub shell running pwd, to attach to the container at /app. This will mount the Gemfile into the container, as well as allow the container to drop a Gemfile.lock back to our code base. The -w /app flag sets our working directory inside the container. And finally, we tell the container to execute bundle install.

At this point we should now have three files in our myApp directory:

  • Dockerfile
  • Gemfile
  • Gemfile.lock

Fresh and clean Rails

Now that we have our code base setup with the basic Ruby stack, it’s time to get Rails up and running. We will be using this Rails container over and over, so let’s make it easy on ourselves and use Docker Compose to automate things.

Create a docker-compose.yml file with this content:

web:
  build: .
  command: rails server -b 0.0.0.0
  ports:
    - '3000:3000'
  volumes:
    - '.:/usr/src/app'

This will let us start and stop a web container, and also let us execute commands within the context of that container. The build section tells Compose that this container needs to be built from the Dockerfile found at the current directory. The command section defines what command is executed when the container starts. We want to run the rails server command. Note that we need to use the -b 0.0.0.0 flag to tell the server to bind to an IP address that we can access from our Mac. Our container has it’s own networking stack, by default when the rails server binds to localhost we won’t be able to access it. The ports section instructs Compose to mount our app at port 3000 both on the outside (the first argument) and inside the container. The volumes sections tells Compose that we want to mount our local directory . at /usr/src/app in the container.

NOTE: The directory /usr/src/app is a convention used by the ruby docker image.

Build the container image with:

docker-compose build

And now we can initialize Rails:

docker-compose run web rails new .

It will ask if it can override your Gemfile, go ahead and let it do so:

Overwrite /usr/src/app/Gemfile? (enter "h" for help) [Ynaqdh] y

At this point we need to make a couple small modifications to the default Rails Gemfile. On or near line 15 we need to uncomment the following line:

# gem 'therubyracer', platforms: :ruby

At the end of the Gemfile we need to add the rails_12factor gem, so that Rails behaves well in a containerized environment:

gem 'rails_12factor'

Now that we have changed the Gemfile we will need to rerun our Docker bundle command:

docker run -it -v $(pwd):/usr/src/app -w /usr/src/app ruby:2.2 bundle install && docker-compose build

You will need to rerun the above commands each time you change the Gemfile. This is because the ruby:2.2-onbuild image executes the bundle install with the global config option frozen enabled, which prevents it from installing any new gems. While it is inconvenient, this is actually a Good Thing. You wouldn’t want to build this project on a colleague’s dev machine, or a production build with gems other than what you specify in the Gemfile.lock.

Serve it up

Now we should have a fully functioning, base Rails app! Let’s test it out by first getting the IP address of the Docker VM and noting it:

boot2docker ip

Then start up the rails server:

docker-compose up

Open up a web browser and access the website at the IP shown above on port 3000. For example:http://192.168.59.103:3000/. You should see the default rails page!

Take it up a notch (or two)

Now that we have a basic Rails app, it’s time to setup a database and really build something useful. Traditionally we would use the sqlite adapter in our development environment, and then deploy to Postrgres or MySQL in production. Now that we have Docker however, we can really step this up and duplicate our production environment right here on our development machine. This ensures that what we see in dev will match up with what we see in production.

Let’s edit our Gemfile again, and replace the following line (around line 7):

gem 'sqlite3'

With this:

gem 'pg'

And rerun our build command:

docker run -it -v $(pwd):/usr/src/app -w /usr/src/app ruby:2.2 bundle install && docker-compose build

Now we need a database server as part of our development stack. Replace your docker-compose.yml file with this:

db:
  image: postgres:9.4
  environment:
    - POSTGRES_PASSWORD=asdfasdf

web:
  build: .
  command: rails server -b 0.0.0.0
  ports:
    - '3000:3000'
  volumes:
    - '.:/usr/src/app'
  links:
    - db

Here we have created a new container called db and have setup an environment variable called POSTGRES_PASSWORD that will set the default password. We made a change to the web container that links it to the db container.

Replace your config/database.yml file to use the postgresql adapter:

default: &default
  adapter: postgresql
  timeout: 5000
  database: myapp_development
  pool: 5
  host: db
  port: 5432
  username: postgres
  password: <%= ENV["DB_ENV_POSTGRES_PASSWORD"] %>

development:
  <<: *default

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: myapp_production

Take notice of the host setting above in the default config section. The database container will always be available to the web container under the name db. Even when we go to production, the database will still be available at the hostname db.

Another neat trick to notice here is the database password configuration. We can access any of the db container’s environment variables from the web container, prefixed with DB_ENV_.

Let’s start the new stack:

docker-compose up

Now we can create the database:

docker-compose run web rake db:create

You should be able to access the Rails default page without any errors.

Building an app with scaffolds

Phew! Now we have a basic Rails application setup to use a Postgres database and all of it runs on our local machine. Let’s create a scaffold with a basic model to get our app actually doing something.

docker-compose run web rails generate scaffold post title body:text published:boolean

Now run the migration:

docker-compose run web rake db:migrate

And access the new scaffold at http://192.168.59.103:3000/posts. Awesome!

You can also run your tests like this:

docker-compose run web rake

Going to production

Now that we have a functioning Rails app that actually does something, let’s take it to production! I’ve found that deploying an app early and often helps to remove any potential gotchas before they get too big. Using Docker locally and deploying to Docker and Tutum in production really makes things easy, so this won’t be difficult.

First thing we need to do is login to the Tutum private repository, if you haven’t done so already:

docker login tutum.co

Now we will create a separate Dockerfile for production use. This will only differ from our original Dockerfile in a few ways. First it will run the Rails asset compiler, secondly it will setup theRAILS_ENV environment variable to production, and lastly it will setup a default command to run. Go ahead and create a new Dockerfile.production with this content:

FROM ruby:2.2-onbuild

RUN rake assets:precompile

ENV RAILS_ENV production

CMD ["rails", "server", "-b", "0.0.0.0"]

We might also want to create a .dockerignore file. This will prevent any containers from picking up files in our filesystem that we don’t need to be in a container. Set it up with a single line:

tmp/

Now we can build a production container that we can send to Tutum’s private registry:

docker build -t tutum.co/username/myapp -f Dockerfile.production .

Replace username with your username. Then we can push it:

docker push tutum.co/username/myapp

After that is pushed we can create a new Stack in Tutum. Navigate tohttps://dashboard.tutum.co/stack/launch/, set the Stack name to whatever you want, and use this for the Stack file:

db:
  image: postgres:9.4
  environment:
    - POSTGRES_PASSWORD=something_Secret

web:
  image: tutum.co/ianneubtest/myapp
  ports:
    - '80:3000'
  links:
    - db
  environment:
    - SECRET_KEY_BASE=my_really_really_really_really_really_really_really_really_long_string

You will notice that this is nearly identical to your docker-compose.yml file. The only difference is that we’ve set an image for the web container, and removed the volumes section. Cool! Hit Create & Deploy and we should be just about good to go.

After the Stack is in the Running state, we can click on the Endpoints tab to find the URL. Open it up in your browser, it will look something like this:

tcp://web.asdf.username.svc.tutum.io:80

Now navigate to your new production service:

http://web.asdf.username.svc.tutum.io:80/posts

Uh oh! Rails is reporting an error. Back in the Tutum dashboard navigate to the stack, and then click on the web service. Then click on the Logs tab.

You should see an error that says:

ActiveRecord::NoDatabaseError (FATAL:  database "myapp_production" does not exist

Oh yeah! To bootstrap a new rails app in production we need to be able to execute rake db:createand rake db:migrate.

Tutum makes this really easy, and we can even do it right from the dashboard.

Click on the Containers tab of the web service. Now click on the blue button that looks like this>_ next to the web-1 container.

This launches us into a terminal running inside our web-1 container.

Once it starts up simply execute the commands like normal:

rake db:create && rake db:migrate

Reload the page, and you should now see the Posts scaffold!

Wrapping up

I hope this tutorial has helped you get setup with a local Docker-ized Ruby on Rails environment and helped you see how easy it is to deploy and work with your application in production on Tutum. This tutorial distills the basis of the real production work that I do with Ruby on Rails on a day to day basis. I’ve found it to be clean and very easy to reproduce across different developer’s machines, as well as CI/CD systems, and ultimately in production.

Let me know in the comments or on twitter if you have any questions!

The biggest drawback to this system, in my opinion, is all the extra typing required on the CLI. In a future post I will describe various ways that I’ve come up with to reduce the verbosity of the various commands above. Subscribe to our blog so you don’t miss it!

You can find the completed code base with an easy Deploy to Tutum button, on the GitHub repository.

UPDATE: 07/10/2015

It was pointed out to me by @ryanbigg on Twitter that using the default rails web server will perform poorly in production. Absolutely! I would not recommend using WEBrick in a full scale production website. This tutorial should help you get Ruby on Rails up and running with Docker, but it is only the start!

Tagged with: , ,
Posted in Tutorial
Categories
%d bloggers like this: