Docker does an excellent job of isolating applications and their environments, making it easier to distribute and replicate 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 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 a Docker container (“dock”).
Our application will be written under Rails 5; database take PostgreSQL. If you want to connect another DBMS, then you need to correct several files.
You can use a predefined template to create an application that is configured using 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
To set the database parameters, we will use the environment variables. You will need them later to connect to the PostgreSQL container.
Edit the config/database.yml
configuration file
If you use the template above, then you do not need to edit the file.
Add the following environment variables to config/database.yml
:
# 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
Our application is ready, it's time for Docker. Start by creating a 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 distro ideal for use in containers. A basic ruby:alpine
image is available in Docker, which we will use.
Start by creating a simple Dockerfile
that needs to be placed in the root directory of the application.
If you use the template above, 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 don't want to use PostgreSQL?
If you use another DBMS (for example, MySQL), then to install the corresponding packages, you will need to make changes in 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 already ready, it's time to start building the Docker image for our application:
We collect image
$ docker build . -t my-app
The image is ready, you can start! 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 several arguments to the docker run
:
-it
- in fact, these are 2 arguments that allow you to interact with the container using the command shell (for example, to pass the key combination Ctrl + C);--env
- allows you to pass environment variables to the container. Here they are used to set the parameters for connecting to the database;--rm
- tells the docker to delete the container after its completion (for example, after pressing Ctrl + C);--publish
- forward 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 launched directly on the host (for example, http://localhost:3000
);--volume
- tells the docker to mount the current directory of the host to 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 re-create the container after each code change.Although the container with the application started up, unfortunately, trying to open the localhost: 3000 link will result in an error:
could not translate host name “db” to address: Name does not resolve
We do not yet have access to the PostgreSQL server application. Now we will fix this by running the Docker container with PostgreSQL:
Council 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 launch the new container with PostgreSQL:
$ docker run -d -it --env POSTGRES_PASSWORD=superSecret123 --env DB_NAME=my-app_development --name mydbcontainer postgres:9.6
The -d
flag is needed to detach the container from the terminal, allowing it to run in the background. We will call the mydbcontainer
, we will need this name further.
Using single-task containers
Docker containers are intended for single use, and their single-tasking nature means that, once they have completed their task, they are stopped and, maybe, deleted.
They are ideal for one-time tasks, such as rails commands (for example, bin/rails db:setup
).
To configure the database in mydbcontainer, we will now execute this command.
Running the rails db:migrate
task using the container
To run a copy of the container with the application, run the following command. Then run in the bin/rails db:setup
container and turn it off.
Note: you will need to set up environment variables to connect to the database (they are inserted into config/database.yml
, which you previously edited).
The option --link
allows you to connect to a container with PostgreSQL ( mydbcontainer
) using the host name 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
The --rm
flag will remove the container after it completes its work.
After executing this command in the mydbcontainer
container, the mydbcontainer
will be configured for the necessary applications. Finally we can run it!
Application launch
Let's run another container based on our application image. Pay attention to several 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 localhost:3000
page in your browser, where you should see our application running completely from Docker!
Docker is a very handy developer tool. Over time, you can transfer all components of your application (DB, redis, sidekiq, cron workflows, etc.) to it.
The next step is to use Docker Compose , which is intended to describe containers and how they interact.
References:
Source: https://habr.com/ru/post/334084/
All Articles