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:
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.
$ git clone firstname.lastname@example.org:DefactoSoftware/Hours.git
Now let’s crack open our
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/secrets.yml before our application will work. Hours is build around deploying to Heroku, and Heroku manages some of this configuration for you.
# 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
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="email@example.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: firstname.lastname@example.org Password: The user is created
Now we’re good to spin up our
$ 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 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.
# 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:
The tests should all be green! Congrats on converting your first application to using Docker.
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.