For text instructions scroll down

In this very first screencast (yay!) I will show you how to set up your local web development environment with Docker and docker-compose.

Watch the screencast directly on YouTube

You can find this and future videos on our YouTube channel.

Introduction

In this first post of our little “Rails on Docker” series, we will set up a development environment using Docker and docker-compose.

You will first need to install both tools. Docker can be obtained by following instructions on https://www.docker.com/. You will also need to install docker-compose.

When you have Docker installed and running on your local machine, you should see something like that when you run docker ps:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Docker is a tool that will allow us to spawn virtual machine “containers”. docker-compose allows us to orchestrate those virtual machines: start them up, link together, forward ports etc.

Create project structure

Our project will consist of main directory, where we will put a docker-compose.yml file, and a webapp directory, where we will put source code of our Rails application. Let’s get those set up:

$ mkdir rails_and_docker_template
$ cd rails_and_docker_template
$ mkdir webapp
$ vim docker-compose.yml

The contents of our docker-compose.yml should be:

version: '3'
services:
  db:
    image: postgres
  webapp:
    build: ./webapp
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ./webapp:/webapp
    ports:
      - "3000:3000"
    depends_on:
      - db

In this configuration file we define two services: a db service that will be based on official PostgreSQL Docker image, and webapp which will be our Ruby on Rails application.

We specify the command that docker-compose will use when starting our application - in our case we start standard Rails server on port 3000. Volumes are directories that will be shared between our host system and a container. Our local webapp directory will be mounted as /webapp inside Docker container.

The Dockerfile

Dockerfile is a recpie that gets executed from top to bottom to build our image. In the first line we specify the base image - I chose Phusion’s baseimage-docker since it is pretty standard Ubuntu, but has some nice tweaks for better Docker support.

In the following lines we install development dependencies, Ruby, Node, Bundler and execute commands to install Ruby dependencies from Gemfile.

FROM phusion/baseimage:0.9.22
CMD ["/sbin/my_init"]

RUN add-apt-repository -y ppa:brightbox/ruby-ng

RUN apt-get update
RUN apt-get install -y libpq-dev git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev nodejs wget autoconf tzdata ruby2.4 ruby2.4-dev rubygems ruby-switch
RUN ruby-switch --set ruby2.4
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN rm -rf /var/lib/gems/2.4.1/cache/*

RUN adduser --disabled-password --gecos "" webapp

RUN mkdir /webapp

WORKDIR /webapp

ADD Gemfile /webapp/Gemfile
ADD Gemfile.lock /webapp/Gemfile.lock

RUN chown -R webapp.webapp /webapp

USER webapp
WORKDIR /webapp

RUN echo "gem: --user-install --env-shebang --no-rdoc --no-ri" > /home/webapp/.gemrc
ENV PATH /home/webapp/.gem/ruby/2.4.0/bin:$PATH
ENV GEM_HOME /home/webapp/.gem/ruby/2.4.0

RUN gem install bundler
RUN gem install rake
RUN bundle install
ADD . /webapp

We also need to add a Gemfile and add empty Gemfile.lock files for now.

$ touch Gemfile.lock
$ vim Gemfile

Our temporary Gemfile will only require one dependency: rails. We need to initially build image that has rails command installed. We will use this command to generate Ruby on Rails project scaffold inside webapp directory. The minimal Gemfile I added has just two lines:

sources https://rubygems.org
gem "rails", "5.1.1"

Let’s build our project:

$ docker-compose build
db uses an image, skipping
Building webapp
Step 1/23 : FROM phusion/baseimage:0.9.22
 ---> 877509368a8d
Step 2/23 : CMD /sbin/my_init
 ---> Using cache
...
Step 23/23 : ADD . /webapp
 ---> 56078ba4194b
Removing intermediate container 4093e813aad3
Successfully built 56078ba4194b
Successfully tagged railsanddockertemplate_webapp:latest

Now we can generate our Rails application:

$ docker-compose run webapp rails new . --force --database=postgresql
Starting railsanddockertemplate_db_1 ...
Starting railsanddockertemplate_db_1 ... done
       exist
      create  README.md
      create  Rakefile
      create  config.ru
      create  .gitignore
      ...
      create  vendor/.keep
      create  package.json
      remove  config/initializers/cors.rb
      remove  config/initializers/new_framework_defaults_5_1.rb
         run  bundle install
      ...

If we look inside our webapp folder now, we will see that it contains standard Rails project scaffold. Next step involves correctly configuring the database for our project. Let’s edit webapp/config/database.yml:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: db
  username: postgres
  password:

development:
  <<: *default
  database: webapp_development

test:
  <<: *default
  database: webapp_test

We need to build our docker image now to include all the added depndencies Rails added to Gemfile:

$ docker-compose build

And finally, create the database:

$ docker-compose run webapp rake db:create

In the next steps, we can start building our application. I will generate some scaffold, and run migrations:

$ docker-compose run webapp rails g scaffold User first_name:string

last_name:string email:string

$ docker-compose run webapp rake db:migrate

We can now start our project and navigate to http://localhost:3000/users to enjoy our first Rails application running inside Docker-based development environment:

$ docker-compose up

We can also run the tests:

$ docker-compose run webapp rake test

Our development environment is ready now. We need to remember to rebuild our docker container image with docker-compose build every time we install new system packages in Dockerfile, or install new dependencies using Gemfile. Otherwise, the changes we make to the source code of application are included immediately in our container.

In the next episode & article I will show you how to deploy our application to Heroku. Stay tuned!

Post by Hubert Łępicki

Hubert is partner at AmberBit. Rails, Elixir and functional programming are his areas of expertise.