【入門】DockerでRuby on Rails + PostgreSQL + React + TypeScriptな環境をビルド

## 背景
今まで何度かDockerに触ろうとしては挫折してきました。Dockerを学ぶ難しさは、どの入門記事を見ても書いてあることが違う、その通りに書いても何らかのエラーが出て止まる、どこを参考にすればいいのか分からない、といったところにあると思います。半ば学ぶことを諦めていたのですが、ある時「Dockerは現代サーバーサイドエンジニアの必須科目だ」と言われてしまい、もう一度挑戦してみた時の記録です。

## 対象読者
- 「Dockerって何？ docker-compose って何？」な読者
- 「とりあえず自分のプロジェクトをDockerで動かしてみたい」な読者

## 参考
- <a href="https://github.com/midwhite/DockerRailsSample" target="_blank">完成版 - GitHub</a>

## 環境
```bash
$ docker -v
Docker version 18.06.0-ce
$ docker-compose -v
docker-compose version 1.22.0
```

## 前提
- Dockerのインストールについては<a href="https://qiita.com/bezeklik/items/a6a7335acaec12edda45" target="_blank">他記事</a>を参照。

## はじめに
### Dockerとは
Dockerは「コンテナ」という技術を用いて環境構築を助けてくれるツールです。通常、アプリケーションの開発環境を用意する際、自分のPCのローカル環境が他の開発者のローカル環境と差がある場合があり、その差に起因して同じ手順での環境構築がうまくいかなかったりします。それを防ぐために、DockerがPCの中に全く新しい別の仮想的なコンピュータを作ってくれます。その作り方の手順を `Dockerfile` というファイルに記述して他の開発者に配布することで、皆が同じ環境を作ることができるということです。

### docker-composeとは
Dockerは1つの仮想的なコンピュータを立ち上げ、その中でアプリケーションを動かします。しかし実際の開発現場では、複数のアプリケーションを必要とすることが多いです。例えばRailsなら、APIサーバーにRails、DBサーバーにPostgreSQL、フロントエンドにReactといったように。
Dockerでこれらを構築しようとすると、1つのDockerfileで1つのサーバーを担当することになります。そこで、その複数のDockerがどのように関連・構成されているのかを定義する役割を負うのが docker-compose です。その構成の定義ファイルが `docker-compose.yml` になります。

## 手順
それでは実際に Docker / docker-compose を用いて Rails + PostgreSQL + React の構成で開発環境を構築してみましょう。

### ディレクトリ構成
下記のような構成でアプリケーションを構築していきます。

```
. # プロジェクトルート
|- api   # Railsアプリケーションを置くディレクトリ
|- front # Reactアプリケーションを置くディレクトリ
|- docker-compose.yml # アプリケーションの構成を定義するファイル
```

### 事前準備
#### Railsプロジェクトを生成
```bash
$ rails new api --api -d postgresql
```

データベースへの接続情報を記述する

```yaml:api/config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: 5

development:
  <<: *default
  database: api_dev

test:
  <<: *default
  database: api_test

production:
  <<: *default
  database: api_production
  username: api
  password: <%= ENV['API_DATABASE_PASSWORD'] %>
```

gemをインストール

```bash
$ bundle install --path vendor/bundle
```

普通に `bundle exec rails s` で起動できる状態にしておく。

#### React + TypeScriptプロジェクトを生成
```bash
$ npm install -g create-react-app
$ create-react-app front --scripts-version=react-scripts-ts
```

普通に `npm start` で起動できる状態にしておく。

### Rails用のDockerfileを用意
Railsプロジェクトのルートに下記のDockerfileを設置する。

```dockerfile:api/Dockerfile
FROM ruby:2.5.1

WORKDIR /app
COPY Gemfile /app
COPY Gemfile.lock /app
COPY . /app

RUN apt-get update -qq && apt-get install -y build-essential nodejs libpq-dev postgresql-client
RUN gem update bundler && bundle install
```

#### 解説
- `FROM ruby:2.5.1` : 公式が用意した既存のRuby 2.5.1用の環境をテンプレートとしてダウンロードする。
- `WORKDIR /app` : コンテナ内のワーキングディレクトリを `/app` に設定する。
- `COPY Gemfile /app` : ホスト側のGemfileをコンテナ側の/appディレクトリ内にコピーする。
- `COPY Gemfile.lock /app` : ホスト側のGemfile.lockをコンテナ側の/appディレクトリ内にコピーする。
- `RUN apt-get update -qq && apt-get install -y build-essential nodejs libpq-dev postgresql-client` : Railsの起動に必要なライブラリを apt-get 経由でインストールする。
- `RUN gem update bundler && bundle install` : gemをインストールする。

### React用のDockerfileを用意
```dockerfile:front/Dockerfile
FROM "node:6-alpine"

WORKDIR /app
```

### docker-composeを定義
```yaml:docker-compose.yml
version: '3.5'

services:
  db:
    image: postgres:10.4
    volumes:
      - postgres-data:/var/lib/postgresql/data
  api:
    build: ./api
    command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    image: rails:dev
    volumes:
      - ./api:/app
      - ./api/vendor/bundle:/app/vendor/bundle
    environment:
      TZ: Asia/Tokyo
      RAILS_ENV: development
    ports:
      - 3000:3000
    depends_on:
      - db
  front:
    build: ./front
    command: yarn start
    volumes:
      - ./front:/app
    ports:
      - 8000:3000
volumes:
  postgres-data:
    driver: local
```

#### 解説
- docker-compose は1つのDockerをserviceという単位で管理する。
- 今回はPostgreSQL用のサーバーを `db` 、Rails用のサーバーを `api` 、React用のサーバーを `front` と名付けて設定していく。
- PostgreSQLは公式が用意した `postgres` という環境（イメージ）があれば、追加でインストールすべきもの等も特に無い。そのためDockerfileを用いず、docker-composeにイメージ名を指定するだけで環境を構築できる。
- RailsとReactについてはDockerfileを用意したので、それぞれ `build` という項目にDockerfileの位置を記入する。
- `command` : 実際にサーバーを起動する時のコマンドを記述する。Rails起動時に `rm -f /app/tmp/pids/server.pid` が挟まっているのは、 docker-compose を停止する度に server.pid が削除されないまま残ってしまう問題に対処するため。
- `volumes` : 記憶領域の指定。ディレクトリ名で指定するパターンと、ボリューム名で指定するパターンがある。ボリューム名を用いる場合は最後に出てくる `volumes` を用いる。ディレクトリ名で指定する場合、 `<ホスト側のディレクトリ>:<対応するコンテナ側のディレクトリ>` と書けば、ホスト側でファイルが変更された時にコンテナ側にすぐ反映されるようになる。
- `environment` : 環境変数の設定。プロパティ名だけ書いて値を書かなければ、コンテナ起動時のホスト側の環境変数が自動で引き継がれるため、SECRET等はこのような方法でコンテナに渡す。
- `posts` : ホストからのアクセスをコンテナ側へ渡すプロキシを設定する。例えば `8000:3000` なら、ホスト側が localhost:8000 でアクセスしてきた場合、コンテナ側の localhost:3000 に渡す。これでコンテナ内のアプリケーションにアクセスできるようになる。
- `depends_on` : 依存関係を設定する。今回の場合、例えばRailsだけを起動しようとしても、依存関係のあるdbも自動で一緒に起動される。

### 起動
```bash
$ docker-compose build
$ docker-compose run front yarn
$ docker-compose run api bundle exec rails db:create
$ docker-compose up
```

#### 解説
- `docker-compose build` : Dockerfileを読み込んで必要なイメージをダウンロード＆構築
- `docker-compose run <コマンド>` : コンテナ側のワーキングディレクトリ上で `<コマンド>` を実行
- `docker-compose up` : servicesに定義された各コンテナを起動
- `docker-compose stop` : servicesに定義された各コンテナを停止

Reactアプリケーションが http://localhost:8000 、Railsアプリケーションが http://localhost:3000 で起動するはず。