Rails5 + MariaDB 10.3 + Nginx をDockerで環境構築

はじめに

まずは、Docker Composeのベースとなるディレクトリ(例として「rails5-docker-compose」)を作成します。このディレクトリに「docker-compose.yml」を置くことになります。

$ mkdir rails5-docker-compose
$ cd rails5-docker-compose

構築する3つのコンテナ、Rails、MariaDB、Nginx、それぞれで管理するためのディレクトリを作成します。

$ mkdir -p mariadb/sql
$ mkdir -p rails/src
$ mkdir nginx

ベースとなるディレクトリのPATHが環境によって異なるので、docker-compose.ymlで参照できる環境設定ファイル「.env」をカレントディレクトリに準備します。

.env

HOSTSRCPATH=c:/Users/foo/devel/rails5-docker-compose

ここでは、ベースとなるディレクトリまでの絶対パスを、「HOSTSRCPATH」という環境変数にセットしておきます。普段私は、Docker Desktop for Windows、つまりWindows上のDockerエンジン(Hyper-V)を利用し、DockerクライアントはWSL上のubuntuのdocker-cliを利用しているので、上のような例になっています。

前は、「c:/Users/foo/」の部分は「~/」でマウント(bind)できていましたが、Docker Desktopをupdateしていつの間にかできなくなりました。

DBコンテナ(MariaDB)

MariaDBは、10.3のDockerイメージを使用しています。

文字コードエンコーディングにUTF-8を利用するので、その設定ファイルを準備しておきます。

mariadb/server.cnf

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
[client]
default-character-set=utf8mb4


次に、コンテナ作成と同時に実行するSQLファイルを、mariadbディレクトリの下のsqlディレクトリ(ディレクトリ名は何でも良い)に入れておきます。ここでは、「dbmaster」というDBユーザ名を登録するSQLを実行させるようにしています。

mariadb/sql/00_init.sql

GRANT ALL PRIVILEGES ON *.* TO 'dbmaster'@'%';

最後に、mariadbディレクトリにDockerfileを作成します。

環境設定ファイルやSQLファイルをコンテナ側の決まったディレクトリにコピーされるようCOPYコマンドを記述します。

/docker-entrypoint-initdb.d」にSQLファイルを放り込んでおけば、最初のコンテナ起動時に自動でファイル名の昇順に実行してくれます。

mariadb/Dockerfile

FROM mariadb:10.3
COPY server.cnf /etc/mysql/mariadb.conf.d/
COPY sql/* /docker-entrypoint-initdb.d/

Appコンテナ(Rails)

Rubyの2.6.5-Slimイメージを使用して、Railsが動くコンテナイメージを構築します。

まず、railsのルートディレクトリのホスト側マウント先として、rails/srcディレクトリを準備します。そこがRailsのルートディレクトリになるので、そこに次のようなGemfileファイルを置いておきます。Railsのバージョンは、5.2.4を指定しています。

rails/src/Gemfile

source 'https://rubygems.org'
gem 'rails', '~> 5.2.4'

また、空のGemfile.lockファイルも置いておきます。

$ touch rails/src/Gemfile.lock

これもDockerfileにてCOPYコマンドでコンテナ内に置かれます。その後、「bundle install」にて、Gemfile.lockが更新されます。

docker-compose にて、ホスト側の rails/src とコンテナ側のRailsルートディレクトリ /opt/sample_app がマウントにより共有されるので、ホスト側のGemfile.lockも更新されることになります。ですが、最初のCOPYの際に、ホスト側にGemfile.lockが無いとエラーになりますので、ここでは最初に空のGemfile.lockファイルを置いておくことになります。

それでは、railsでディレクトリにDockerfileを用意します。ベースとしては、
クィックスタート: Compose と Rails | Docker ドキュメントhttps://matsuand.github.io/docs.docker.jp.onthefly/compose/rails/
を参考にしています。

rails/Dockerfile

FROM ruby:2.6.5-slim
ENV LANG C.UTF-8
RUN apt-get update -qq && \
  apt-get install -y build-essential curl tzdata git \
  default-libmysqlclient-dev libmariadb-dev mariadb-client \
  && rm -rf /var/lib/apt/lists/*
RUN 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 yarn
ENV APP_ROOT /opt/sample_app
WORKDIR $APP_ROOT
COPY src/Gemfile $APP_ROOT/Gemfile
COPY src/Gemfile.lock $APP_ROOT/Gemfile.lock
RUN bundle install
COPY src $APP_ROOT
RUN mkdir -p $APP_ROOT/tmp/sockets && \
  mkdir -p $APP_ROOT/tmp/pids && \
  touch $APP_ROOT/tmp/sockets/.keep && \
  touch $APP_ROOT/tmp/pids/.keep

まず、FROMの行ですが、「ruby 2.6.5-slim」をベースイメージとしています。slimでない「ruby 2.6.5」イメージの場合は840MBありますが、「ruby 2.6.5-slim」は147MB程です。ただ、その分開発ライブラリ関係が入っていないので、「build-essential」や「git」などを apt-get install する必要があります。

4番目の「RUN」コマンドの部分は、「yarn」をインストールするための記述です。JQueryやBootstramなど、yarnではなくgemでインストール管理する場合は不要です。

Railsのルートディレクトリ「/opt/sample_app」を環境変数 $APP_ROOT で参照するようにし、「WORKDIR $APP_ROOT」によってカレントディレクトリをそのRailsのルートディレクトリにします。

その後、Gemfileと(最初は空の)Gemfile.lock をコンテナ側にコピーし、「bundle install」を実行します。その後、ホスト側の「rails/src」ディレクトリ配下の内容をRailsルートディレクトリにコピーしますが、最初は何もありません。

「tmp/sockets」や「tmp/pids」のディレクトリは、ホスト側の「rails/src」ディレクトリにbindマウントではなく、volumeマウントで管理することにしているので、コンテナビルドのたびにmkdirさせています。

Webコンテナ(Nginx)

Webコンテナは、Nginx 1.16.1 をベースイメージにしています。Dockerfileは次の通り。

nginx/Dockerfile

FROM nginx:1.16.1
RUN rm -f /etc/nginx/conf.d/*
ADD nginx.conf /etc/nginx/conf.d/sample_app.conf
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

設定ファイルは次の通り、pumaを利用するかたちになっています。

nginx/nginx.conf

upstream puma {
  # Socket file path written in /opt/sample_app/config/puma.rb
  server unix:///opt/sample_app/tmp/sockets/puma.sock;
}
server {
  listen 80;
  server_name localhost;

  access_log /var/log/nginx/access_log;
  error_log /var/log/nginx/error_log;

  root /opt/sample_app/public;
  
  client_max_body_size 100m;
  error_page 404 /404.html;
  error_page 505 502 503 504 /500.html;
  keepalive_timeout 10;

  try_files $uri/index.html $uri @puma;

  location @puma {
    proxy_read_timeout 300;
    proxy_connect_timeout 300;
    proxy_redirect off;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://puma;
  }
  location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
    expires max;
    break;
  }
}

docker-compose.yml

Docker Composeの設定情報である「docker-compose.yml」は以下のとおりです。

docker-compose.yml

version: '3.2'
services:
  db:
    build:
      context: ./mariadb
    volumes:
      - type: bind
        source: ${HOSTSRCPATH}/mariadb/sql
        target: /docker-entrypoint-initdb.d
      - type: volume
        source: mariadb_data
        target: /var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: PASSWORD
      MYSQL_USER: dbmaster
      MYSQL_PASSWORD: PASSWORD
      TZ: "Asia/Tokyo"
    ports:
      - '3306:3306'
  app:
    build:
      context: ./rails
    volumes:
      - type: bind
        source: ${HOSTSRCPATH}/rails/src
        target: /opt/sample_app
      - type: volume
        source: bundle_data
        target: /usr/local/bundle
      - type: volume
        source: public_data
        target: /opt/sample_app/public
      - type: volume
        source: tmp_data
        target: /opt/sample_app/tmp
        command: bundle exec puma -C config/puma.rb
    environment:
      - "TZ=Asia/Tokyo"
    ports:
      - '3000:3000'
    depends_on:
      - db
  web:
    build:
      context: ./nginx
    volumes:
      - type: volume
        source: public_data
        target: /opt/sample_app/public
      - type: volume
        source: tmp_data
        target: /opt/sample_app/tmp
    environment:
      - "TZ=Asia/Tokyo"
    depends_on:
      - app
    ports:
      - '80:80'
  volumes:
    bundle_data:
      driver: local
    mariadb_data:
      driver: local
    public_data:
      driver: local
    tmp_data:
      driver: local
  networks:
    default:
      driver: bridge
      ipam:
        driver: default
        config:
          - subnet: 192.168.10.0/24


まず、「${HOSTSRCPATH}」は、「.env」ファイルにて、ホスト側のベースとなるディレクトリパスが定義されている環境変数「HOSTSRCPATH」の値を参照します。

db:」エントリ部分では、MariaDBのrootのパスワード「MYSQL_ROOT_PASSWORD」、Railsで使用するDBのユーザ「MYSQL_USER」、そしてそのパスワード「MYSQL_PASSWORD」を指定しています。「app:」エントリ部分では、depents_on: にて db を指定しており、APPコンテナが起動する前に、DBコンテナが起動するようにしています。また、command: にて、pumaを起動するようにしています。

web:」エントリ部分では、そのpumaが起動した後で、nginxが動くので、depends_on: にて app を指定しています。

bundleコマンドにてインストールされたGemファイル、データベースファイル、railsのpublic配下のファイル、後railsのtmp配下のファイルは、bindマウントではなく、それぞれvolumeマウントにしています。

以上、ここまで準備したファイルの配置関係は次のとおりです。

$ tree -aL 3

├── .env
├── docker-compose.yml
├── mariadb
│     ├── Dockerfile
│     ├── server.cnf
│     └── sql
│             └── 00_init.sql
├── nginx
│     ├── Dockerfile
│     └── nginx.conf
└── rails
    ├── Dockerfile
    └── src
            ├── Gemfile
            └── Gemfile.lock
5 directories, 11 files

コンテナのビルド

まずは、Railsプロジェクト(sample_app)の作成と、Gemfile、Gemfile.lock を更新するために、次のコマンドにて、appコンテナを一時的に構築し起動させます。

$ docker-compose run --rm app rails new . --force --no-deps --database=mysql --skip-coffee --skip-turbolinks --skip-sprockets --webpack

Webpackerではなく、従来通りアセットパイプライン(Sprockets)を利用される場合は、オプションの「–skip-coffee –skip-turbolinks –skip-sprockets –webpack」を指定する必要はありません。

問題なく実行が完了すれば、rails/src 配下に、Railsプロジェクトができあがり、Gemfile及びGemfile.lockの内容もきちんと更新されています。

$ ls -aF rails/src
./               .git/          Gemfile      Rakefile*        bin/       db/  node_modules/      public/  tmp/
../              .gitignore*    Gemfile.lock app/             config/    lib/ package.json*      storage/ vendor/
.browserslistrc* .ruby-version* README.md*   babel.config.js* config.ru* log/ postcss.config.js* test/    yarn.lock*

$ ls -l rails/src/Gemfile*
-rw-r--r-- 1 furuya furuya 2112 6月 14 11:17 rails/src/Gemfile
-rw-r--r-- 1 furuya furuya 5231 6月 14 11:18 rails/src/Gemfile.lock

コンテナは、APPコンテナの前に先に起動しているはずのDBコンテナが、Exitの状態で構築されています。

$ docker-compose ps
         Name                      Command                  State     Ports
---------------------------------------------------------------------
rails5-docker-compose_db_1   docker-entrypoint.sh mysqld   Exit 1

$ docker ps -a
CONTAINER
ID            IMAGE                      COMMAND                  CREATED          STATUS                      PORTS  NAMES
ef6d289ac763  rails5-docker-compose_db   "docker-entrypoint.s…"  14 minutes ago   Exited (1) 13 minutes ago          rails5-docker-compose_db_1

Dockerイメージとしても、新たに2つ作られていますが、APPコンテナイメージの方は、再度ビルドにて作り直すことになります。

$ docker images
REPOSITORY                     TAG                 IMAGE ID             CREATED             SIZE
rails5-docker-compose_app      latest              f85d1c0c9d62         12 minutes ago      603MB
rails5-docker-compose_db       latest              ad037d7a1a32         15 minutes ago      343MB
nginx                          1.16.1              dfcfd8e9a5d3         7 weeks ago         127MB
:

ここで、一旦作成されたDBコンテナを、「docker-compose down」にて削除します。

$ docker-compose down

$ docker-compose down
Removing rails5-docker-compose_db_1 ... done
Removing network rails5-docker-compose_default

コンテナの最終的なビルド

では、Gemfile.lockも完成されたので、最終的なビルド「docker-compose build」を行います。

$ docker-compose build
$ docker-compose build
Building db
Step 1/3 : FROM mariadb:10.3
---> 47dff68107c4
Step 2/3 : COPY server.cnf /etc/mysql/mariadb.conf.d/
---> Using cache
---> e743e09d0f71
:
Removing intermediate container ec55f74aef5b
---> 947add7c0572
Successfully built 947add7c0572
Successfully tagged rails5-docker-compose_web:latest

上手くいけば、以上のようにWebコンテナのイメージまで作成されます。
現時点では、コンテナは作成されていません。

$ docker-compose ps
Name   Command   State   Ports
------------------------------

ここで、Railsと連携するデータベースのユーザ名等を指定するために、rails/src/config/database.yml を更新します。

rails/src/config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: dbmaster
  password: PASSWORD
  host: db
development:
  <<: *default
  database: sample_app_development
:

username:」、「password:」、それぞれ docker-compose.yml にて指定した「MYSQL_USER」、「MYSQL_PASSWORD」と同じ値を指定します。またrailsが起動しているコンテナと、DBのコンテナは別なので、「host:」にてDBコンテナのホスト名を参照する「db」と指定します。

では、「docker-compose up」にて、それぞれ生成されたDockerイメージからDockerコンテナを構築、起動させます。

$ docker-compose up
$ docker-compose up
Creating network "rails5-docker-compose_default" with driver "bridge"
Creating rails5-docker-compose_db_1 ... done
Creating rails5-docker-compose_app_1 ... done
Creating rails5-docker-compose_web_1 ... done
Attaching to rails5-docker-compose_db_1, rails5-docker-compose_app_1, rails5-docker-compose_web_1
db_1   | 2020-06-14 11:48:16 0 [Note] mysqld (mysqld  10.3.18-MariaDB-1:10.3.18+maria~bionic) starting as process 1 ...
:
db_1   | 2020-06-14 11:48:17 0 [Note] InnoDB: Buffer pool(s) load  completed at 200614 11:48:17
app_1  | Puma starting in single mode...
app_1  | * Version 3.12.6 (ruby 2.6.5-p114), codename: Llamas in  Pajamas
app_1  | * Min threads: 5, max threads: 5
app_1  | * Environment: development
app_1  | * Listening on tcp://0.0.0.0:3000
app_1  | Use Ctrl-C to stop

ここまで起動したら、別のターミナル立ち上げて、APPコンテナにて「rake db:create」を実行します。

まずは、別のターミナルウィンドウにて、それぞれコンテナのステータスを確認します。

[別のターミナル]

 $ docker-compose ps
         Name                        Command               State            Ports
-----------------------------------------------------------------------------------------
rails5-docker-compose_app_1   bundle exec puma -C config ...   Up       0.0.0.0:3000->3000/tcp
rails5-docker-compose_db_1    docker-entrypoint.sh mysqld      Up       0.0.0.0:3306->3306/tcp
rails5-docker-compose_web_1   /bin/sh -c /usr/sbin/nginx ...   Up       0.0.0.0:80->80/tcp

では、APPコンテナにて、「rake db:create」コマンドにてデータベースの作成。

[別のターミナル]

$ docker-compose exec app rake db:create
$ docker-compose exec app rake db:create
Created database 'sample_app_development'
Created database 'sample_app_test'

この段階で、既にRailsは動いています。

[別のターミナル]

$ curl -I http://localhost:3000/
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
:

しかし、Nginxの方はまだpumaの設定が完了していないので動いていません。

[別のターミナル]

$ curl -I http://localhost/
HTTP/1.1 502 Bad Gateway
Server: nginx/1.16.1
Date: Sun, 14 Jun 2020 02:51:36 GMT
:

元のターミナルに戻って、[Ctrl] + [C]にて、コンテナの起動を強制停止します。

:
app_1  | Use Ctrl-C to stop
^CGracefully stopping... (press Ctrl+C again to force)
Stopping rails5-docker-compose_web_1 ... done
Stopping rails5-docker-compose_app_1 ... done
Stopping rails5-docker-compose_db_1  ... done

rails/src/config/puma.rb を書き換えます。

$ cp -p rails/src/config/puma.rb rails/src/config/puma.rb.bak
$ vi rails/src/config/puma.rb

変更箇所は次の通りです。

$ diff -u  rails/src/config/puma.rb.bak rails/src/config/puma.rb
--- rails/src/config/puma.rb.bak     2020-06-14  11:56:51.889286500 +0900
+++ rails/src/config/puma.rb    2020-06-14 11:59:04.766103600  +0900
@@ -9,7 +9,7 @@
# Specifies the `port` that Puma will listen on to receive  requests; default is 3000.
#
-port        ENV.fetch("PORT") { 3000 }
+#port        ENV.fetch("PORT") { 3000 }
# Specifies the `environment` that Puma will run in.
#
@@ -35,3 +35,5 @@
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart
+bind "unix:///opt/sample_app/tmp/sockets/puma.sock"
+stdout_redirect "/opt/sample_app/log/puma.stdout.log",  "/opt/sample_app/log/puma.stderr.log", true

再度、コンテナを起動します。今度は、もうコンテナは構築済みなので、「docker-compose up」ではなく「docker-compose start」コマンドになります。

$ docker-compose start
$ docker-compose start
Starting db  ... done
Starting app ... done
Starting web ... done

コンテナ起動のログを確認します。

$ docker-compose logs
Attaching to rails5-docker-compose_web_1, rails5-docker-compose_app_1, rails5-docker-compose_db_1
app_1  | Puma starting in single mode...
app_1  | * Version 3.12.6 (ruby 2.6.5-p114), codename: Llamas in  Pajamas
app_1  | * Min threads: 5, max threads: 5
app_1  | * Environment: development
app_1  | * Listening on tcp://0.0.0.0:3000
app_1  | Use Ctrl-C to stop
app_1  | - Gracefully stopping, waiting for requests to finish
app_1  | === puma shutdown: 2020-06-14 11:53:56 +0900 ===
app_1  | - Goodbye!
app_1  | Puma starting in single mode...
app_1  | * Version 3.12.6 (ruby 2.6.5-p114), codename: Llamas in  Pajamas
app_1  | * Min threads: 5, max threads: 5
app_1  | * Environment: development
app_1  | * Listening on  unix:///opt/sample_app/tmp/sockets/puma.sock
app_1  | Use Ctrl-C to stop
db_1   | 2020-06-14 11:48:16 0 [Note] mysqld (mysqld  10.3.18-MariaDB-1:10.3.18
:

これで3000番ポートではなく80番ポートでアクセスできることになります。

$ curl -I http://localhost:3000/
curl: (52) Empty reply from server

$  curl -I http://localhost/
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Sun, 14 Jun 2020 03:02:12 GMT
:

以上、こちらで説明したファイル群は、GitHubにて公開しています。https://github.com/tamochia/rails5-docker-compose


失敗例1

Dockerエンジンを動かした状態で、しばらくサスペンドのまま放っておいて、久しぶりにDockerビルドを実行した際に apt-get update などをやると、時刻のズレが原因で、次のようなエラーが発生する。

E: Release file for http://deb.debian.org/debian/dists/buster-update
s/InRelease is not valid yet (invalid for another 14h 42min 39s). Upd
ates for this repository will not be applied.
E: Release file for http://security.debian.org/debian-security/dists
/buster/updates/InRelease is not valid yet (invalid for another 11h 1
2min 1s). Updates for this repository will not be applied.
ERROR: Service 'app' failed to build:

とでたら、Docker Desktop for Windows (Docker Server) をrestart

【参考】「apt update で Release file for xxx is not valid yet って言われる問題 – Qiita
https://qiita.com/nobuoka/items/1bb8cbb5af7be5259547

失敗例2

次のような場合は、Docker Desktop for Windows のrestartだけでは解決しない。

ERROR: for rails5-docker-compose-db Cannot start service db: driver f
ailed programming external connectivity on endpoint rails5-docker-comp
ose-db (43b035e20b03ddf493e34aa540649923059475ad6e8585e0344ae158b7166
634): Error starting userland proxy: /forwards/expose/port returned u
nexpected status: 500
ERROR: for db Cannot start service db: driver failed programming ext
ernal connectivity on endpoint rails5-docker-compose-db (43b035e20b03
ddf493e34aa540649923059475ad6e8585e0344ae158b7166634): Error starting
userland proxy: /forwards/expose/port returned unexpected status: 500
ERROR: Encountered errors while bringing up the project.

とでたら、次の通りに行う。

  1. Docker Desktop for Windows を終了させる
  2. タスクマネージャーにて、プロセス「com.docker.backend.exe」を停止させる
  3. Docker Desktop for Windows を起動させる

【参考】「Dockerを再起動してもdriver failed programming external connectivity on endpointでコンテナを起動できない時の対処法 – Qiitahttps://qiita.com/masaoops/items/e79157ec89cd991ef8d2

失敗例3

tmp/sockets/puma.sock」が無いなどエラーがでてNginxが動かないとき。

rails/src/config/puma.rb」のこの行にコメントを入れておかないと、puma動かず tmp/sockets/puma.sock が作られない。

rails/src/config/puma.rb

##port        ENV.fetch("PORT") { 3000 }

失敗例4

Railsを動かしているときに、常に次のようなメッセージが表示される。

Started GET "/static_pages/home" for 127.0.0.1 at 2020-04-26 05:44:45
+0900
Cannot render console from 192.168.10.1! Allowed networks: 127.0.0.1,
::1, 127.0.0.0/127.255.255.255

docker-compose.ymlで指定した、Dockerネットワーク(bridge)のネットワーク 192.168.10.0/24 のゲートウェイのIPアドレス192.168.10.1」をWhitelistに入れてやる。

rails/src/config/environments/development.rb

config.web_console.whitelisted_ips = '192.168.10.1'

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中