Set up a Rails 6.0 app with Ruby 2.7 and Docker

I’ve been playing around with Ruby again to brush up my skills. Here’s how I’m setting up a new Rails app using Ruby 2.7.1 and Docker.

First things first, lets set up our Dockerfile.

mkdir myapp && cd myapp
touch Dockerfile

And in the Dockerfile add the following:

FROM ruby:2.7.1

RUN apt-get update && apt-get install -y \
  curl \
  build-essential \
  libpq-dev &&\
  curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
  curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
  echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
  apt-get update && apt-get install -y nodejs yarn postgresql-client

WORKDIR /srv

COPY Gemfile /srv/Gemfile
COPY Gemfile.lock /srv/Gemfile.lock
RUN bundle install
COPY . /srv

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

EXPOSE 3000

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

This will install nodejs, yarn, and postgres dependencies, copy over your Gemfile and install the gem dependencies, and then boot up the rails server.

So far so good. Only problem is we haven’t created our Gemfile yet. Let’s do that now.

touch Gemfile
touch Gemfile.lock

Open your Gemfile and add the following:

source 'https://rubygems.org'

gem 'rails', '~>6'

Now lets add the entrypoint.sh file that we’re copying over in the Dockerfile

touch entrypoint.sh

And add the following:

#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process
exec "$@"

Making good progress. Now we’ll add our docker-compose.yml file to orchestrate the different services that will compose our app. Right now we just have two: the app and the database

Create a docker-compose.yml file and add the following:

version: '3.7'

services:
  myapp:
    build: .
    command: bash -c "bundle exec rails s -p 3000 -b '0.0.0.0'"
    container_name: myapp
    entrypoint: entrypoint.sh
    volumes:
      - .:/srv
    ports:
      - "3000:3000"
    depends_on:
      - db
    restart: always
  db:
    image: postgres
    volumes:
    - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: "root"
    restart: always

We now have everything we need to create our rails app. Let’s do that now by running the following command:

docker-compose run myapp rails new . --force --no-deps --database=postgresql

This will create the Rails app in our current directory. In addition to adding all the files, it will also update our Gemfile with the required dependencies for the Rails app. Since we have new dependencies, we need to rebuild our image.

docker-compose build

Connect to the database

You should be able to start your Rails app now, but you won’t be able to connect to the database.

Open up config/database.yml and replace the contents with the following:

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

development:
  <<: *default
  database: myapp_development


test:
  <<: *default
  database: myapp_test

Next, add the dotenv gem to your Gemfile so we can easily load the environment variables

gem 'dotenv-rails', groups: [:development, :test]

Then, create a .env file to store our database credentials and add the following:

DB_USERNAME=postgres
DB_PASSWORD=root

You should now be able to boot the app by running:

docker-compose up

Finally, create the development database. Open a new terminal window and run the following:

docker-compose run myapp rake db:create

You should see output similar to this:

$ docker-compose run myapp rake db:create
Starting myapp_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'

View the app

That’s it! You should now be able to navigate to http://localhost:3000 and view your Rails app.