前置き
いつでも簡単にRailsの開発環境を構築したかったのでDockerで開発環境を構築することにしました。公式の手順やご参考にさせて頂いた記事を基に少し自動化してみることにしました。筆者のDockerの知識は入門書を一読した程度なので学習も兼ねています。なお、個々のコマンドの説明などは既に素晴らしい記事がたくさんありますので作成したファイルにコメントを振ることをメインにします。
そのまま使えるはずの状態でGitHubにも公開しました。興味ありましたら使ってください
動作環境
OS | MacOS High Sierra 10.13.6 |
Docker | Docker for Mac CE 18.06.1-ce-mac73 (26764) |
構成
GitHubに公開しています。併せてご覧ください。
Docker周りだけを管理したいディレクトリと永続化だけを管理したいディレクトリに分けました。
以降、粛々とファイルとその説明が続きます。「とにかくGitHubのコードを動かしたい」方はこちらまで飛んでください
$ tree
├── README.md
├── docker # DockerやDocker Compose
│ ├── containers # 各コンテナ(イメージ)
│ │ ├── mysql # MySQL 5.7
│ │ │ ├── Dockerfile # MySQL 5.7のDockerファイル
│ │ │ ├── grant_user.sql # 初期セットアップでユーザを作成するスプリプト
│ │ │ └── my.cnf # 初期セットアップで反映するmy.cnf
│ │ ├── nginx # Nginx 1.15.2
│ │ │ ├── Dockerfile # NginxのDockerファイル
│ │ │ └── nginx.conf # 初期セットアップで反映するnginx.conf
│ │ └── rails # Rails ~>5.2.0
│ │ ├── Dockerfile # Dockerファイル
│ │ ├── Gemfile # 初期セットアップで反映するGemfile
│ │ ├── application.rb # 初期セットアップで反映するapplication.rb
│ │ ├── database.yml # 初期セットアップで反映するdatabase.yml
│ │ ├── docker-entrypoint.sh # コンテナ起動時動作するシェルスクリプト
│ │ └── puma.rb # 初期セットアップで反映するpuma.rb
│ ├── docker-compose.yml # Composeファイル
│ └── environments # 環境変数定義
│ ├── common.env # 各コンテナ共通
│ └── db.env # DB接続用MySQL関連
└── volumes # 永続化したリソース
├── app # Railsのソースコード このディレクトリは最初は無し
├── db # MySQLのデータ このディレクトリは最初は無し
├── ssl # SSL証明書
│ ├── privkey.pem # 秘密鍵
│ └── server.crt # サーバー証明証
└── web # Nginxのログ このディレクトリは最初は無し
Composeファイル
version: '3' # 特に意思は無いが新しいバージョンで。
services: # 各コンテナ(サービス)
db: # MySQLのコンテナ 「db」と命名
build: containers/mysql # Dockerファイルのパス
env_file: # 環境変数
- ./environments/common.env # 各コンテナ共通
- ./environments/db.env # DB接続用MySQL関連
volumes: # 永続化
- ../volumes/db/data:/var/lib/mysql # MySQLのデータ
app: # Railsのコンテナ 「app」と命名
build: containers/rails # Dockerファイルのパス
env_file: # 環境変数
- ./environments/common.env # 各コンテナ共通
- ./environments/db.env # DB接続用MySQL関連
command: bundle exec puma -C config/puma.rb # 実行するコマンド
volumes: # 永続化
- ../volumes/app:/app # Railsのソースコード
depends_on: # 起動する順番
- db # 「db」後で起動
web: # Nginxのコンテナ 「web」と命名
build: containers/nginx # Dockerファイルのパス
env_file: # 環境変数
- ./environments/common.env # 各コンテナ共通
volumes: # 永続化
- ../volumes/app:/app # 静的ファイル
- ../volumes/web/log:/var/log/nginx/ # Nginxのログ
- ../volumes/ssl:/etc/nginx/cert/ # SSL証明書
ports: # 解放ポート
- 443:443 # HTTPS この開発環境はHTTPSのみ想定
depends_on: # 起動する順番
- app # 「app」後で起動
db(MySQL)コンテナ
Dockerファイル
# MySQL 5.7
FROM mysql:5.7
# 初期セットアップで利用するmy.cnfをイメージへコピー
# アーカイブを展開する必要などが無ければADDで無くてCOPYで良い。
COPY my.cnf /etc/mysql/conf.d
# 初期セットアップで実行したいスクリプトをイメージへコピー
COPY grant_user.sql /docker-entrypoint-initdb.d
コピーするファイルの概要
ホスト | イメージ | 概要 |
---|---|---|
docker/containers/mysql/my.cnf | /etc/mysql/conf.d/ | 文字コード |
docker/containers/mysql/grant_user.sql | /docker-entrypoint-initdb.d/ | Railsアプリ向けDBアカウント |
Docker Comopseから設定される環境変数
TZ=Asia/Tokyo
MYSQL_ROOT_PASSWORD=root
MYSQL_USER=rails
MYSQL_PASSWORD=rails
Docker Comopseから設定される永続化
ホスト | コンテナ | 概要 |
---|---|---|
volumes/db/data | /var/lib/mysql | MySQLのデータ |
app(Rails)コンテナ
Dockerファイル
# 公式を参考
FROM ruby:2.5.1
RUN apt-get update -qq && apt-get install -y build-essential default-libmysqlclient-dev nodejs
# MySQLのイメージを参考にコンテナ起動時のエントリポイントを作成
# 元のスクリプトをコピー
COPY docker-entrypoint.sh /usr/local/bin/
# 権限付与
RUN chmod 744 /usr/local/bin/docker-entrypoint.sh
# 永続化されるパスへ直接ファイルをコピーしてしまうとマウント時にホスト側の内容で上書きされ
# イメージ構築でコピーされたファイルは全て消失してしまうので一時ディレクトリで作業
ENV READY_RAILS_DIR=/ready_rails
# ディレクトリ作成とワークディレクトリに指定
WORKDIR $READY_RAILS_DIR
# 初期セットアップで反映する各ファイルをコピー
COPY Gemfile .
COPY application.rb .
COPY database.yml .
COPY puma.rb .
# 永続化でマウントされるパスをワーキングディレクトリに指定
WORKDIR /app
ENTRYPOINT ["docker-entrypoint.sh"]
コピーするファイルの概要
ホスト | イメージ | 概要 |
---|---|---|
docker/containers/rails/Gemfile | /ready_rails/ | gem ~> Rails 5.2.0 |
docker/containers/rails/application.rb | /ready_rails/ | タイムゾーンを設定したapplication.rb |
docker/containers/rails/database.yml | /ready_rails/ | ユーザ/パスワードを設定したdatabase.yml |
docker/containers/rails/puma.rb | /ready_rails/ | NginxとPuma間のUNIXドメインソケットを設定したpuma.rb |
docker/containers/rails/docker-entrypoint.sh | /usr/local/bin/ | 後述 |
コンテナ起動時のENTRYPOINT
MySQL公式のENTRYPOINTを参考に自動化したシェルスクリプトを作成しました。
#!/bin/bash
set -e
# ホストにあるRailsのソースコードが無く永続化でマウントされた場合
# /app/Gemfileは存在しないはず。
if [ ! -e Gemfile ]; then
# 一時ディレクトリからGemfileをコピー
cp -a $READY_RAILS_DIR/Gemfile .
touch Gemfile.lock
bundle install
rails new . -d mysql -f
# 初期セットアップのファイル群をコピー
cp -a $READY_RAILS_DIR/database.yml \
$READY_RAILS_DIR/puma.rb \
$READY_RAILS_DIR/application.rb config/
# NginxとPuma間をUNIXドメインソケットで通信するためのディレクトリを作成
mkdir -p tmp/sockets
# 初期セットアップのファイル群が/appにコピーされて
# /appがホストにマウントされたので一時ディレクトリは削除
rm -r $READY_RAILS_DIR
# depends_on指定でdb(MySQL)コンテナが先に起動されている。
# 順番に起動するものの先行のコンテナの準備が整うまで待機は行えない。
# つまりDBの準備が完了していない状況があるので待機する。
until rails db:drop &> /dev/null; do
>&2 echo "MySQL is unavailable - sleeping"
sleep 1
done
# DBの準備が整ってからdb:create
rails db:create
# ホストにあるRailsのソースコードがあり永続化でマウントされた場合
# この場合/app/Gemfileは存在する一時ディレクトリは残っているはず。
elif [ -e $READY_RAILS_DIR ]; then
bundle install
rm -r $READY_RAILS_DIR
fi
exec "$@"
Docker Comopseから設定される環境変数
TZ=Asia/Tokyo
MYSQL_ROOT_PASSWORD=root
MYSQL_USER=rails
MYSQL_PASSWORD=rails
Docker Comopseから設定される永続化
ホスト | コンテナ | 概要 |
---|---|---|
volumes/app/ | /app | Railsのソースコード(Nginxと共用) |
web(Nginx)コンテナ
Dockerファイル
FROM nginx:1.15.2
# 初期セットアップで反映するファイルをコピー
COPY nginx.conf /etc/nginx/conf.d/app.conf
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
コピーするファイルの概要
ホスト | イメージ | 概要 |
---|---|---|
docker/containers/nginx/nginx.conf | /etc/nginx/conf.d/app.conf | 後述 |
WEBサーバーの設定
トリッキーな設定は無いと思いますが簡単に補足します。
GitHubに公開しているものはSSL証明書については「*.example.com」の自己署名SSL証明書(所謂「オレオレ証明書」)を利用しています。筆者の環境ではLet's EncryptのSSL証明書を利用しています。
余談ですがssl on | offは廃止になったようです。
# PumaのSocketを指定
upstream app {
server unix:///app/tmp/sockets/puma.sock;
}
server {
# 今回HTTPは使用しない。ssl onは廃止のよう・・・。
listen 443 ssl;
server_name devnokiyo.example.com;
ssl_certificate /etc/nginx/cert/server.crt;
ssl_certificate_key /etc/nginx/cert/privkey.pem;
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Railsの静的ファイルはNginxで返す。
root /app/public;
try_files $uri/index.html $uri @app;
client_max_body_size 10m;
error_page 404 /404.html;
error_page 505 502 503 504 /500.html;
location @app {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-CSRF-Token $http_x_csrf_token;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://app;
}
}
Docker Comopseから設定される環境変数
TZ=Asia/Tokyo
Docker Comopseから設定される永続化
ホスト | コンテナ | 概要 |
---|---|---|
volumes/app/ | /app/ | Nginxのドキュメントルートの/app/public PumaのUNIXドメインソケット |
volumes/web/log/ | /var/log/nginx/ | ログ |
volumes/ssl/ | /etc/nginx/cert/ | SSL証明書 |
実行してみましょう!
Docker Composeで起動
だいぶ簡単でしたが各ファイルの説明が終わりましたので実行して確認します。
-
GitHubから一式ダウンロードします。
-
dockerディレクトリへ移動します。
$ cd docker
-
ビルドします。(初回なので起動でも構いません)
$ docker-compose build --no-cache Building db Step 1/3 : FROM mysql:5.7 ---> 563a026a1511 Step 2/3 : COPY my.cnf /etc/mysql/conf.d ---> 691e66ddfe3c : : Successfully built 5f5831d39d90 Successfully tagged docker_web:latest ```
-
起動します。
$ docker-compose up Creating network "docker_default" with the default driver Creating docker_db_1 ... done Creating docker_app_1 ... done Creating docker_web_1 ... done Attaching to docker_db_1, docker_app_1, docker_web_1 db_1 | Initializing database db_1 | 2018-09-09T12:32:32.270957Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details). : : db_1 | Version: '5.7.23' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) : : app_1 | Fetching gem metadata from https://rubygems.org/.......... app_1 | Fetching gem metadata from https://rubygems.org/. app_1 | Resolving dependencies... app_1 | Fetching rake 12.3.1 app_1 | Installing rake 12.3.1 : : app_1 | * bin/rake: spring inserted app_1 | * bin/rails: spring inserted app_1 | Created database 'app_development' app_1 | Created database 'app_test' app_1 | Puma starting in single mode... app_1 | * Version 3.12.0 (ruby 2.5.1-p57), codename: Llamas in Pajamas app_1 | * Min threads: 5, max threads: 5 app_1 | * Environment: development app_1 | * Listening on unix:///app/tmp/sockets/puma.sock app_1 | Use Ctrl-C to stop
ブラウザからアクセス
hostsファイルにdevnokiyo.example.comをループバックIPで設定してhttps://devnokiyo.example.com/ にアクセスします。不正な証明書ということでエラーが出ると思いますが便宜上例外を許可してください。以下はChromeの例ですが表示自体はされています。
前述のとおり筆者専用環境ではLet's EncryptのSSL証明書を利用しています。ご参考までに正常な証明書扱いの画像も載せておきます。独自ドメインを所有しておりdevxxx.devnokiyo.comで取得した例になります。
Scaffoldを作成
問題なく表示されたら取り敢えずscaffoldでDB周りを確認します。
$ docker-compose exec app rails g scaffold food name:string price:integer calorie:integer
Running via Spring preloader in process 55
invoke active_record
create db/migrate/20180909124600_create_foods.rb
create /models/food.rb
:
:
invoke scss
create /assets/stylesheets/scaffolds.scss
$ docker-compose exec app rails db:migrate
== 20180909124600 CreateFoods: migrating ======================================
-- create_table(:foods)
-> 0.0234s
== 20180909124600 CreateFoods: migrated (0.0235s) =============================
CRUDで確認
https://devnokiyo.example.com/foods にアクセスします。
New/Edit/Show/Destroyを確認してみてください。
終わりに
基礎的なコマンドの説明よりは「筆者はこうやった」的なことをメインに記事を書いてみました。Dockerの入門書やネットで情報だけ得て「分かった気」になっていましたが、実際に手を動かすと上手くいかないこともありました。やはり手を動かしてみると大きく理解が進みますね。しばらくこれをベースにRailsをやっていこうと思います。