Dockerise existing rails application
Tags: docker, webdev, rails, ruby
Published: 2022-01-28 14:55:56

In this blog we will see how to dockerise the existing rails application.

For demo purpose, I have created a basic rails application
App configuration are as follows,

- Ruby: Ruby 3.1.0
- Rails version: Rails 7.0.1
- Database: Postgres
- Model: Article(title: string, content: text)

## Step 1: Create a Dockerfile

A docker file contains the information to create the container image. We need a basic docker container image. These basic images are already created on docker hub. Docker Hub contains predefined ruby images. Our application ruby version is 3.1.0.

We will use https://hub.docker.com/layers/ruby/library/ruby/3.1.0/images/sha256-6155f0235e95166b49508ff0acbea7958bec53017bd307938e4dceb2cbd99c63?context=explore from docker hub.
ruby:3.1.0, here 3.1.0 is the tag of the image.

Create a file with name Dockerfile without extension inside rails root folder.

Here is the docker file

```
# syntax=docker/dockerfile:1
FROM ruby:3.1.0
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Configure the main process to run when running the image
CMD ["rails", "server", "-b", "0.0.0.0"]
```

Line 1: fetches the ruby:3.1.0 image from docker hub if not present already.
Line 2: Adds nodejs and postgresql-client to container environment.
Line 3: It creates a working directory. Any command after this will be run inside this folder.
Line 4 & Line 5: copies Gemfile and Gemfile.lock from host inside working directory. It will copy these files from current folder to container myapp folder.
Line 6: runs the bundle install inside our container in working directory. This will install all the gems we need to run the application in our container.

Next, provide an entrypoint script to fix a Rails-specific issue that prevents the server from restarting when a certain server.pid file pre-exists. This script will be executed every time the container gets started.
entrypoint.sh consists of:

```
#!/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 (what's set as CMD in the Dockerfile).
exec "$@"
```

Add this script to rails root folder as entrypoint.sh
Next it will copy it in /usr/bin/ inside container
Run chmod on it.
Next command exposes port 3000 of container to host. Last line starts the rails server.

RUN sudo docker build .

This will build the docker image 
i.e pull the ruby image and run the commands the dockerfile.

Our app will also need postgres database.
For this we will use postgres image from DockerHub.
We will directly specify this in our docker compose file.

##Step 2: Docker Compose

Create a docker-compose.yml file inside the rails app root directory.

```
version: "3.3"
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

```

It consists of 2 services.

- db
  1. image: postgres will fetch postgres image from docker hub to our local
  2. To persist our database through multiple container reruns we use volumes, where we specify local host location and map it to postgres container data location. Docker postgres image volume location is /var/lib/postgresql/data. You will find this in docker hub otherwise after image pull run `docker inspect postgres`
and see the path in Volumes key.
- web
  1. For postgres we use default image from docker hub. However, for rails we will use the dockerfile.
  2. build . will build the dockerfile.
  3. next we removed the server pid and started the server.
  4. next we are binding the current folder to myapp folder in container.Any changes made inside our current directly will be visible in /myapp and eventually it will show us the latest content as we edit the file.
  5. Map post 3000 of host to post 3000 of container.
  6. It depends on db service.

##Step 3: Run Docker Compose
Start container using docker-compose file

Command to start:
`docker-compose up`

Running this will give us errors as we have not configured our database and not connected the rails app with postgres.

1. We need to create database in our postgres container. We will create it using our rails commands
2. For that we need to configure database.yml right.
use same username and password which you passed to postgres
as environment variables. In order to connect our rails app with database we have to provide correct host in database.yml
3. In this case host is db i.e. our service name.
4. Docker compose file creates a network of different services for us. In order to communicate with different services in this network we specify the service names.

Sample database.yml contents:
```
database: dockerise_rails_test
username: postgres
password: password
host: db
```

5. After configuring the database.yml, create database using
`docker-compose run web rails db:create`
6. Run database migrations:
`docker-compose run web rails db:migrate`
7. Open Rails console:
`docker-compose run web rails console`
Or
`docker ps`
get the name or container id and run
`docker exec -it dockerise_rails_web_1 rails c`
8. Now run docker-compose file
`docker-compose up`
Both db and web services starts
Docker compose logs shows us that both db_1 and web_1 have started.
![docker-compose up console output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ac7mvw3gpw8gwjzs0feu.png)
Our web application:
 ![Rails application running on port 3000](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6pg6eyifk61qzv69iha.png)

[Github repository with dockerfile and docker-compose](https://github.com/kaushikhande/dockerise_rails)

Command to stop both rails and database container:
`docker-compose down`

References:

1. https://docs.docker.com/samples/rails/