RailsアプリをDockerで開発するための手順

## 追記

コードの全体像がわかりにくいって意見をもらったのでgithubにコードおきました。
(りーどみーがわかりにくいとかあればPRいただけると幸せです)
https://github.com/togana/sample-rails-docker

versionとか少し変わってたりDockerfileの中で実行してるコマンドが少し変わったりしてますが、大きく変わっているところはないので参考にしてください！

## 追記2

docker-machineの共有ファイルを利用している場合、Rails5からファイル変更しても検知できなくなっていました。

ファイルの変更を検知しているのは `config.file_watcher` という設定です。
ここがバージョンアップに伴い変更されていました。

`ActiveSupport::FileUpdateChecker` から `ActiveSupport::EventedFileUpdateChecker` になったようです。[コミットログ参照](https://github.com/rails/rails/commit/de6ad5665d2679944a9ee9407826ba88395a1003)

それぞれ下記のような特性があります。

- `ActiveSupport::EventedFileUpdateChecker` はファイルの変更イベントを受けて検知する
- `ActiveSupport::FileUpdateChecker` はポーリングしてファイルの変更を監視する

docker-machineの共有ファイルを利用している場合、ファイルの変更イベントが検証した限り、起きないみたいです。

解決策が見つかるまでdevelopmentsのコンフィグを書き換えて対応することにしました。

```diff:app/config/environments/developments.rb
- config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+ config.file_watcher = ActiveSupport::FileUpdateChecker
```

## ゴール

1. Dockerfileを使い、親イメージからRailsアプリイメージを構築できる
1. docker-composeを用いて複数コンテナの管理ができる
1. Railsアプリが開発できる

## Railsアプリの作成
Railsアプリを作成するには rails new をしなくてはなりません。そのためにプロジェクトディレクトリを用意します。
(すでに開発中の場合は[Railsアプリイメージの構築](#Railsアプリイメージの構築)までとばしてください)

```bash:プロジェクトディレクトリ生成
$ mkdir project_name
$ cd project_name
```

しかしローカルの環境にRubyを導入したくないのです(rbenvとかから開放されたい)。なのでrails newすらDockerを利用します。[dockerhub](https://hub.docker.com)から[Rubyの公式イメージ](https://hub.docker.com/r/_/ruby/)を取得して実行します。今回は2.3.0を使用しますが、他のバージョンが使用したい場合は[Tags](https://hub.docker.com/r/library/ruby/tags/)を参照してください。

```bash:ruby:2.3.0のイメージを取得
$ docker pull ruby:2.3.0
```

Gemfileを生成する為にbundle initを実行します。

```bash:Gemfile生成
$ docker run --rm -v "$PWD":/usr/src/project_name -w /usr/src/project_name ruby:2.3.0 bundle init
```

```text:オプションに関する補足
docker run オプション image名:タグ名 実行コマンド
--rm: 実行後のコンテナを削除します。指定しない場合はゴミが残り続けます。
-v: ホストのディレクトリをコンテナ内のディレクトリにマウントします。"$PWD"はカレントディレクトリを意味します。
-w: ワーキングディレクトリを指定します。
```

そして、下記のようにGemfileを編集します。
今回はversionを4.2.6にしています。他のバージョンがいい場合は変更してください。

```rb:Gemfile
source "https://rubygems.org"
gem 'rails', '4.2.6'
```

イメージ内にgemを埋め込む為に、Gemfile.lockを生成しておきます。

```bash
$ touch Gemfile.lock
```

ここから、親イメージを読み込んで独自のアプリイメージを構築していきます。

```rb:Dockerfile
FROM ruby:2.3.0

ENV APP_ROOT /usr/src/project_name

WORKDIR $APP_ROOT

RUN apt-get update && \
    apt-get install -y nodejs \
                       mysql-client \
                       postgresql-client \
                       sqlite3 \
                       --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT

RUN \
  echo 'gem: --no-document' >> ~/.gemrc && \
  cp ~/.gemrc /etc/gemrc && \
  chmod uog+r /etc/gemrc && \
  bundle config --global build.nokogiri --use-system-libraries && \
  bundle config --global jobs 4 && \
  bundle install && \
  rm -rf ~/.gem
```

```text:補足情報
FROM: 親イメージの指定
ENV: 環境変数の設定
WORKDIR: ワーキングディレクトリの指定
COPY: ファイルをイメージにコピー
RUN: コマンド実行
```

ビルドしてイメージを作成します。

```bash
$ docker build -t developer_name/project_name .
```

```text:オプションに関する補足
docker build .: カレントディレクトリのDockerfileをビルド
-t: 開発者名/プロジェクト名でイメージ名が作成される
```

イメージができているか確認します。

```bash
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
developer_name/project_name   latest              4d1652dba54d        12 minutes ago      826.2 MB
ruby                          2.3.0               7ca70eb2dfea        5 days ago          725.4 MB
```

イメージができたので、railsの雛形をrails new . で生成します。Gemfileがコンフリクト起こすのでYを選択して上書きしましょう。

```bash
$ docker run --rm -it -v "$PWD":/usr/src/project_name developer_name/project_name rails new . -BT
       exist  
   identical  README.rdoc
   identical  Rakefile
   identical  config.ru
   identical  .gitignore
    conflict  Gemfile
Overwrite /usr/src/project_name/Gemfile? (enter "h" for help) [Ynaqdh] Y
```

```text:オプションに関する補足
-it: コンテナのプロセスに対してttyを割り当てる
-BT: bundle install 無し、テストツール無し(minitest使わないこと多いので)
```

## <a name="Railsアプリイメージの構築"></a> Railsアプリイメージの構築

アプリケーションのイメージを作っていきます。
必要なツールを導入、ソースコードを焼き付けて、デフォルトでwebサーバーの起動が行えるようにDockerfileを修正します。

```rb:Dockerfile
FROM ruby:2.3.0

ENV APP_ROOT /usr/src/project_name

WORKDIR $APP_ROOT

RUN apt-get update && \
    apt-get install -y nodejs \
                       mysql-client \
                       postgresql-client \
                       sqlite3 \
                       --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT

RUN \
  echo 'gem: --no-document' >> ~/.gemrc && \
  cp ~/.gemrc /etc/gemrc && \
  chmod uog+r /etc/gemrc && \
  bundle config --global build.nokogiri --use-system-libraries && \
  bundle config --global jobs 4 && \
  bundle install && \
  rm -rf ~/.gem

COPY . $APP_ROOT

EXPOSE  3000
CMD ["rails", "server", "-b", "0.0.0.0"]
```

ビルドしてイメージを再作成します。

```bash
$ docker build -t developer_name/project_name .
```

アプリを動作させるだけならイメージにソースコードが埋め込まれてるので下記で動作します。

```bash:実行コマンド
$ docker run -d -p 3000:3000 developer_name/project_name
```

開発時は、コンテナにローカルディレクトリをマウントすると、ソースコードの変更が即座に反映されるのでいいと思います。
コンフィグなどサーバーの再起動が必要な場合はrestartする必要があります。

```bash:開発時実行コマンド
$ docker run -d -p 3000:3000 -v "$PWD":/usr/src/project_name developer_name/project_name
```

```bash:restart方法
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
05a6270674cc        developer_name/project_name   "rails server -b 0.0."   6 seconds ago       Up 5 seconds        0.0.0.0:3000->3000/tcp   goofy_lumiere
$ docker restart 05a6270674cc
05a6270674cc
```

## docker-composeによるコンテナ管理

先ほど作ったDockerfileをdocker-composeで管理していきます。

```yaml:docker-compose.yml
version: '2'
services:
  app:
    build: .
    environment:
      RAILS_ENV: development
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/project_name
```

docker-compose.ymlができたら再ビルドする。

```bash
$ docker-compose build
```

ボリューム設定やポート設定が、docker-compose.yml内に記述されているので、起動コマンドがすっきりしてるのがわかる。

```bash
$ docker-compose up -d
```

```text:オプションに関する補足
-d: バックグラウンド実行
```

ここからはDBのコンテナを立てて連携していく。今回はmysqlを使うことにする。

sqlite3からmysql2にGemfileを変更。

```diff:Gemfile
- gem 'sqlite3'
+ # gem 'sqlite3'
+ gem 'mysql2'
```

sqlite3のdatabaseの設定からmysql2の設定に変更。
urlは環境変数を使用して設定する。

```rb:config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  port: 3306
  pool: 5
  timeout: 5000
  url: <%= ENV['DATABASE_URL'] %>
development:
  <<: *default
  database: db_development

test:
  <<: *default
  database: db_test

production:
  <<: *default
  database: db_production
```

docker-compose.ymlにmysqlの設定をする。

```yaml:docker-compose.yml
version: '2'
services:
  app:
    build: .
    environment:
      RAILS_ENV: development
      DATABASE_URL: mysql2://root:pass@mysql:3306
      MYSQL_ROOT_PASSWORD: 'pass'
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/project_name
    links:
      - mysql
  mysql:
    image: mysql:5.7.10
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
    ports:
      - '3306:3306'
    volumes:
      - mysql-data:/var/lib/mysql
volumes:
  mysql-data:
    driver: local
```

develop環境のためパスワードをハードコーディングしてるがプロダクション環境では環境変数に記述すること。

```text:mysqlのパスワードの設定
MYSQL_ROOT_PASSWORD: 'pass'
```

DBの設定ができたので再ビルドして実行してみる。

```bash
$ docker-compose build
$ docker-compose up -d
```

appコンテナとmysqlコンテナが動作しているか確認する。

```bash
$ docker-compose ps
    Name           Command          State           Ports     
-------------------------------------------------------------
vagrant_app_1       rails server -b 0.0.0.0    Up              0.0.0.0:3000->3000/tcp
vagrant_mysql_1     /entrypoint.sh mysqld   Up              0.0.0.0:3306- >3306/tcp
```

ブラウザから動作確認

```text:err
Unknown database 'db_development'
```

データベースが生成されていないのでアプリケーションエラーがでている。
実際にデータベースを作成してエラーが解消されることを確認する。

```bash
$ docker-compose run --rm app rake db:create
```

ブラウザから動作確認問題なく動いていることがわかる。
上記のようにrakeコマンドを使ってマイグレーション等も行えるので活用すると開発がスムーズに行える。

## 不要なプロセスとイメージを削除する

ステータスがexitedのプロセスを削除して、イメージ名が`<none>`のイメージを削除するワンライナー

```bash
$ docker ps -aq -f "status=exited" | xargs docker rm -v && docker images -q -f "dangling=true" | xargs docker rmi && docker volume ls -qf dangling=true | xargs docker volume rm
```

### docker1.13以上を使っている方はこちら

```bash
$ docker system prune -f
```

## 僕がよく使うコマンド

```bash
# DB作りたくなったら
$ docker-compose run --rm app rake db:create

# マイグレーションしたくなったら
$ docker-compose run --rm app rake db:migrate

# seed実行したくなったら
$ docker-compose run --rm app rake db:seed

# コントローラー作成したくなったら(controller_nameを変更してどうぞ)
$ docker-compose run --rm app rails generate controller controller_name

# Model作成したくなったら(model_nameを変更してどうぞ 引数にname:stringとかでnameカラムを作れます。)
$ docker-compose run --rm app rails generate model model_name name:string 

# ルーティング変更したくなったら(config/routes.rbを編集後実行)
$ docker-compose run --rm app rake routes
```