Getting started with Ruby on Rails in Docker
- Transfer
Docker does a great job of isolating applications and their environments, facilitating the distribution and replication of states between different environments (dev, test, beta, prod, etc.). Its use allows you to get rid of the problem "everything works on my machine" and helps to easily scale the application as it grows.
Docker is especially good when an application has many dependencies or if it requires the use of specific versions of libraries and configuration tools.
In this article we will take a simple Rails application and prepare it for use in the Docker container (“dockerize”).
Required Components
Our application will be written under Rails 5; take the database PostgreSQL. If you want to connect another DBMS, you will need to fix several files.
You can use a predefined template to create an application that is configured with Dockerfile
and config/database.yml
:
$ rails new --database=postgresql --skip-bundle --template=https://gist.githubusercontent.com/cblunt/1d3b0c1829875e3889d50c27eb233ebe/raw/01456b8ad4e0da20389b0b91dfec8b272a14a635/rails-docker-pg-template.rb my-app
$ cd my-app
Database configuration
To set the database parameters we will use environment variables. They will be needed later to connect to the container with PostgreSQL.
Edit the configuration file config/database.yml
If you used the above template, then you do not need to edit the file.
Add to your config/database.yml
environment variables:
# config/database.yml
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host: db
username: <%= ENV.fetch('POSTGRES_USER') %>
password: <%= ENV.fetch('POSTGRES_PASSWORD') %>
development:
<<: *default
database: my-app_development
test:
<<: *default
database: my-app_test
production:
<<: *default
database: my-app_production
Create Dockerfile
Our app is ready, it's time for Docker. Let's start with the creation Dockerfile
. This is a simple text file that contains instructions for creating an image for the application. It is used to set dependencies, set default environment variables, copy code to a container, etc.
To save disk space, I prefer to use the basic alpine-linux Ruby image. Alpine linux is a tiny linux distribution ideal for container use. In Docker, a basic image is available ruby:alpine
, which we will use.
Let's start by creating a simple Dockerfile
one that needs to be placed in the root directory of the application.
If you used the above template, then you do not need to edit the file.
# /path/to/app/Dockerfile
FROM ruby:2.3-alpine
# Установка часового пояса
RUN apk add --update tzdata && \
cp /usr/share/zoneinfo/Europe/London /etc/localtime && \
echo "Europe/London" > /etc/timezone
# Установка в контейнер runtime-зависимостей приложения
RUN apk add --update --virtual runtime-deps postgresql-client nodejs libffi-dev readline sqlite
# Соберем все во временной директории
WORKDIR /tmp
ADD Gemfile* ./
RUN apk add --virtual build-deps build-base openssl-dev postgresql-dev libc-dev linux-headers libxml2-dev libxslt-dev readline-dev && \
bundle install --jobs=2 && \
apk del build-deps
# Копирование кода приложения в контейнер
ENV APP_HOME /app
COPY . $APP_HOME
WORKDIR $APP_HOME
# Настройка переменных окружения для production
ENV RAILS_ENV=production \
RACK_ENV=production
# Проброс порта 3000
EXPOSE 3000
# Запуск по умолчанию сервера puma
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
What if I do not want to use PostgreSQL?
If you use another DBMS (for example, MySQL), then to install the appropriate packages you will need to make changes to the Dockerfile.
You can search for required packages using the following Docker command:
$ docker run --rm -it ruby:2.3-alpine apk search --update mysql | sort
...
mariadb-client-libs-10.1.22-r0
mariadb-dev-10.1.22-r0
mariadb-libs-10.1.22-r0
mysql-10.1.22-r0
mysql-bench-10.1.22-r0
...
Since the Dockerfile is ready, it's time to start building the Docker image for our application:
Putting the image together
$ docker build . -t my-app
The image is ready, you can begin! Start the container with the following command:
$ docker run --rm -it --env RAILS_ENV=development --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=superSecret123 --publish 3000:3000 --volume ${PWD}:/app my-app
We passed docker run
several arguments to the team :
-it
- in fact, these are 2 arguments that allow you to interact with the container using the shell (for example, to pass the keyboard shortcut Ctrl + C);--env
- allows you to pass environment variables to the container. Here they are used to set database connection parameters;--rm
- tells the docker to remove the container after it finishes working (for example, after pressing Ctrl + C);--publish
- Forwards port 3000 of the container to port 3000 of the host. Thus, we have the opportunity to connect to the service as if it was running directly on the host (for example,http://localhost:3000
);--volume
- tells the docker to mount the current host directory into the container. Thus, you get the opportunity to edit the code on the host, but at the same time it will be available in the container. Without this, you would have to recreate the container after each code change.
Starting the database container
Although the container with the application started, an attempt to open the localhost: 3000 link will unfortunately lead to an error:
could not translate host name “db” to address: Name does not resolve
We do not have a PostgreSQL server available to the application. Now we will fix it by running the Docker container with PostgreSQL:
Tip. Do not forget that in Docker one container must perform one and only one function.
In our case, there will be 2 containers: one for the application and one for the database (PostgreSQL).
Launching a new container with PostgreSQL
To stop (and delete) the container with the application, press Ctrl + C, then start a new container with PostgreSQL:
$ docker run -d -it --env POSTGRES_PASSWORD=superSecret123 --env DB_NAME=my-app_development --name mydbcontainer postgres:9.6
The flag is -d
needed in order to disconnect the container from the terminal, allowing it to work in the background. We will call the container mydbcontainer
, we will need this name further.
Using Single-Task Containers
Docker containers are intended for single use, and their one-tasking nature means that as soon as they have completed their task, they will be stopped and maybe removed.
They are ideal for one-time tasks, such as rails commands (for example, bin/rails db:setup
).
To configure the database in mydbcontainer, we now run this command.
Выполнение задачи rails db:migrate
с использованием контейнера
Для запуска копии контейнера с приложением выполните следующую команду. Затем запустите в контейнере bin/rails db:setup
и выключите его.
Обратите внимание: вам потребуется настроить переменные окружения для соединения с базой данных (они вставляются в config/database.yml
, который вы ранее редактировали).
Опция --link
позволит подключиться к контейнеру с PostgreSQL (mydbcontainer
), используя имя хоста db
:
$ docker run --rm --env RAILS_ENV=development --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=superSecret123 --link mydbcontainer:db --volume ${PWD}:/app my-app bin/rails db:create db:migrate
Флаг --rm
удалит контейнер после завершения его работы.
После выполнения этой команды в контейнере mydbcontainer
будет настроенная под нужны приложения база данных. Наконец-то мы сможем его запустить!
Запуск приложения
Let's launch another container based on the image of our application. Pay attention to some additional options of the command:
$ docker run --rm -it --env RAILS_ENV=development --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=superSecret123 --publish 3000:3000 --volume ${PWD}:/app --link mydbcontainer:db my-app
=> Puma starting in single mode...
=> * Version 3.8.2 (ruby 2.4.1-p111), codename: Sassy Salamander
=> * Min threads: 5, max threads: 5
=> * Environment: development
=> * Listening on tcp://0.0.0.0:3000
=> Use Ctrl-C to stop
Open the page in your browser localhost:3000
where you should see our application that works completely from under Docker!
Next steps
Docker is a very convenient developer tool. Over time, you can transfer all the components of your application into it (DB, redis, sidekiq, cron workflows, etc.).
The next step is to use Docker Compose , which is designed to describe containers and how they interact.
References: