Today we’re going to take an existing Ruby on Rails application and convert it so that it be developed/deployed using Docker.
Goal for this Tutorial:
- Dockerizing an existing rails application.
Up to this point we’ve been working with “hello world” applications when working with Docker, but today we’re going to take an existing application (that I didn’t write) and get it to work with Docker.
Dockerizing a Rails Application
The application that we’re going to convert today is an open source Rails application for time tracking called “Hours”. This particular application requires a few different services:
- Ruby/Rails
- PostgreSQL
- Memcached
Since this project isn’t currently set up to be used with Docker, the first thing we’ll do is clone the repo and create a docker-compose.yml
file to lay out all of our services.
Cloning:
$ git clone git@github.com:DefactoSoftware/Hours.git
Now let’s crack open our docker-compose.yml
:
docker-compose.yml
version: "2"
volumes:
db-data:
external: false
services:
cache:
image: memcached:1.4-alpine
db:
environment:
POSTGRES_USER:
POSTGRES_PASSWORD:
image: postgres:9.5
volumes:
- db-data:/usr/local/pgsql/data
jobs:
env_file: .env
build: .
volumes:
- .:/usr/src/app
command: bundle exec rake jobs:work
depends_on:
- db
app:
env_file: .env
build: .
volumes:
- .:/usr/src/app
ports:
- "8080:8080"
depends_on:
- db
- cache
Now we need to set up our Dockerfile
so that we can build an image to run our application on. This application uses Capybara-webkit for testing so we’ll need to install some extra dependencies for that to work:
FROM ruby:2.3.1
RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends \
postgresql-client \
nodejs \
qt5-default \
libqt5webkit5-dev \
&& apt-get -q clean \
&& rm -rf /var/lib/apt/lists
WORKDIR /usr/src/app
COPY Gemfile* ./
RUN bundle install
COPY . .
CMD bundle exec unicorn -c ./config/unicorn.rb
We’ll need to create config/database.yml
and config/secrets.yml
before our application will work. Hours is build around deploying to Heroku, and Heroku manages some of this configuration for you.
Database:
config/database.yml
# config/database.yml
default: &default
adapter: postgresql
host: <%= ENV["POSTGRES_HOST"] %>
username: <%= ENV["POSTGRES_USER"] %>
password: <%= ENV["POSTGRES_PASSWORD"] %>
encoding: utf8
min_messages: warning
pool: 2
timeout: 5000
development:
<<: *default
database: hours_development
test:
<<: *default
database: hours_test
production:
<<: *default
database: hours_production
config/secrets.yml
development:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
development:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
You’ll notice that we’ve used quite a few environment variables thus far. Environment variables are potentially the easiest way to configure our application, but they can be tedious. Thankfully, docker-compose
allows us to use a .env
file to store the values that we would like to use in our containers and we don’t need to set them in our own environment.
Let’s create our own .env
file now by copying the .sample.env
file and making some adjustments.
POSTGRES_HOST=db
POSTGRES_USER=hours
POSTGRES_PASSWORD=super_secure
SECRET_KEY_BASE=development_secret
GMAIL_USERNAME="example@mail.com"
GMAIL_PASSWORD="password"
HELPFUL_URL=https://example.io/incoming_message
HELPFUL_ACCOUNT=helpful_account
SINGLE_TENANT_MODE=true
S3_BUCKET_NAME="s3_bucket_name"
AWS_ACCESS_KEY_ID="aws_access_key_id"
AWS_SECRET_ACCESS_KEY="aws_secret_access_key"
Now we’re ready to take our group of containers for a spin.
Spinning up our Containers
Like we had to when learning to develop a Rails application using Docker, we’re going to need to do a few things before our application will actually run. First, we’re going to need to create our database. We can do that by starting our services that are going to run from images, those shouldn’t have any issues.
$ docker-compose up -d db cache
This will create the network that all of our containers connect to to communicate with one another and also start our memcached and postgres containers.
Next up, we’ll need to build our app
image before we can run commands. This is going to need to install all of the gems and dependencies so it will likely take awhile. A nearly identical image will be used for our jobs
container so we’ll build that now also.
$ docker-compose build app jobs
After we’ve build our image we can run some commands with it. First, we need to create and migrate our database:
$ docker-compose run --rm app rake db:create db:migrate
Second, we want to create a user for ourselves within the application.
$ docker-compose run --rm app rake create_user
First name: Coder
Last name: Journey
email: keith@coderjourney.com
Password:
The user is created
Now we’re good to spin up our app
and jobs
containers.
$ docker-compose up -d app jobs
Before moving any further we should make sure that all of our containers are running using docker-compose ps
:
$ docker-compose ps
Name Command State Ports
-------------------------------------------------------------------------------
hours_app_1 /bin/sh -c bundle exec uni ... Up 0.0.0.0:8080->8080/tcp
hours_cache_1 docker-entrypoint.sh memcached Up 11211/tcp
hours_db_1 /docker-entrypoint.sh postgres Up 5432/tcp
hours_jobs_1 bundle exec rake jobs:work Up
Now we can head over to localhost:8080
and check out our new time tracking application.
Fixing the Test Suite
Right now, if we try to run the test suite we’ll get some errors because it can’t find certain pieces of the page to click on. This is caused by this application already using a .env
file to load environment variables into the application and us having explicitly defined environment variables in our containers. We’re going to get around this by overloading the environment in the test so that it’s always the same.
spec/spec_helper.rb
# Environment set to "test" above
require "dotenv"
Dotenv.overload(".sample.env")
# environment file loaded below
It’s important that we put these two lines in the right spot so that we set the environment variables before loading the rails app for our tests, just in case those ENV values are stored in constants somewhere.
Now we can see if it worked by running:
$ rake
The tests should all be green! Congrats on converting your first application to using Docker.
Recap
All of the code for this tutorial can be found on Github.
I’m looking for feedback on upcoming topics. Head over and answer my single question survey go let me know what you think I should cover.