【Rails】Dockerで環境構築をする方法

# 開発環境
・Docker: 19.03.8
・Docker-Compose: 1.25.5
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

# 実装

## 1.作業ディレクトリを作成・移動

フォルダ名は`myapp`(なんでもいい)として進めていきます。

```terminal:ターミナル
$ mkdir myapp
```

```terminal:ターミナル
$ cd myapp
```

## 2.`Dockerfile`を作成・編集

```terminal:ターミナル
$ vi Dockerfile
```

```docker:Dockerfile
FROM ruby:2.7.1
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# コンテナー起動時に毎回実行されるスクリプトを追加
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# メインプロセスの起動
CMD ["rails", "server", "-b", "0.0.0.0"]
```

### 【解説】

#### ★ 概要
`FROM` : 使用するイメージとバージョンを設定
`RUN` : コマンドの実行
`WORKDIR` : 作業ディレクトリを設定
`COPY` :　ローカルのファイルをコンテナへコピー
`ENTRYPOINT` : 一番最初に実行するコマンドを設定
`EXPOSE` : コンテナがリッスンするport番号を設定
`CMD` :　イメージ内部のソフトウェア実行

#### ★ 詳細

**◎ 使用するイメージとして、2020/7/19現在最新安定版を取得する。**

```docker
FROM ruby:2.7.1
```

**◎ Node.jsとPostgreSQLのパッケージをインストールする。**

```docker
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
```

**◎ コンテナ内に`myapp`ディレクトリを作成する。**

```docker
RUN mkdir /myapp
```

**◎ 作業ディレクトリを、上記で作成した`myapp`に設定する**

```docker
WORKDIR /myapp
```

**◎ ローカルの`Gemfile`を、コンテナ内の`myapp`へコピーする。**

```docker
COPY Gemfile /myapp/Gemfile
```

**◎ ローカルの`Gemfile.lock`を、コンテナ内の`myapp`へコピーする。**

```docker
COPY Gemfile.lock /myapp/Gemfile.lock
```

**◎ `bundle install`を実行する。**

```docker
RUN bundle install
```

**◎ カレントディレクトリ(ローカルのmyapp)を、コンテナ内の`myapp`へコピーする。**

※ `.` = カレントディレクトリ

```docker
COPY . /myapp
```

**◎ ローカルの`entrypoint.sh`を、コンテナ内の`usr/bin`へコピーする。**

```docker
COPY entrypoint.sh /usr/bin/
```

**◎ 上記でコピーした`entrypoint.sh`への、アクセス権限を許可する。**

```docker
RUN chmod +x /usr/bin/entrypoint.sh
```

**◎ コンテナを立ち上げた際に、1番最初に実行されるコマンドとして`entrypoint.sh`を設定する。**

```docker
ENTRYPOINT ["entrypoint.sh"]
```

**◎ ポート番号を`3000`に設定する。**

```docker
EXPOSE 3000
```

**◎ Railsサーバーを立ち上げる。**

```docker
CMD ["rails", "server", "-b", "0.0.0.0"]
```

## 3.`Gemfile`を作成・編集

Railsのバージョン指定を行います。
今回はRails6に対応するようにしていきます。

```terminal:ターミナル
$ vim Gemfile
```

```ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6'
```

## 4.`Gemfile.lock`を作成

```terminal:ターミナル
$ touch Gemfile.lock
```

## 5.`entrypoint.sh`を作成・編集

```terminal:ターミナル
$ vi entrypoint.sh
```

```sh:entrypoint.sh
#!/bin/bash
set -e

# Rails に対応したファイル server.pid が存在しているかもしれないので削除する。
rm -f /myapp/tmp/pids/server.pid

# コンテナーのプロセスを実行する。（Dockerfile 内の CMD に設定されているもの。）
exec "$@"
```

## 5.`docker-compose.yml`を作成・編集

```terminal:ターミナル
$ vi docker-compose.yml
```

```yml:docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
```

### 【解説】

`version` : docker-composeのバージョンを設定する。
`services` : この下のハッシュにサービスを作る。命名は自由だが、通常はwebとdbと名付ける。
`image` : 使用するimage（dbでPostgreSQLを指定）
`volumes` : ディレクトリのマウント設定（dbデータなどを残せる）
`build` : Dockerfileなどがあるパス（基本的にカレントディレクトリ）
`command` : コマンド（server.pidファイルを削除してからrailsサーバー起動)
`ports` : ポート番号(ホスト：コンテナで設定)
`depends_on` :　依存関係を示していて、起動順を指定できる。ここではdb→webへと起動する。

## 6.ディレクトリ構造を確認

```terminal:ターミナル
myapp
- Dockerfile
- Gemfile
- Gemfile.lock
- entrypoint.sh
- docker-compose.yml
```

## 7.プロジェクトをビルド

```terminal:ターミナル
$ docker-compose run web rails new . --force --no-deps --database=postgresql --skip-bundle
```

### 【解説】

`--force` : Gemfileを上書き
`--no-deps` : リンクしたサービスを起動しない
`--database=postgresql` : DBにPostgreSQLを指定
`--skip-bundle` : bundleをスキップ

## 8.`bundle install`を実行

```terminal:ターミナル
Could not find gem 'pg (>= 0.18, < 2.0)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.
```

`7` で上記のようなエラーが出たかと思うので、解決する為に下記コマンドを実行します。
Dockerイメージををビルドする際に、`bundle install`してくれます。

```terminal:ターミナル
$ docker-compose build
```

## 9.`database.yml`を編集

```yml:config/database.yml
# 変更前
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

# 変更後
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: 5
```

## 10.コンテナを起動

```terminal:ターミナル
$ docker-compose up
```

### ※下記エラーが出る場合

```terminal:ターミナル
Error: No such file or directory @ rb_sysopen - /myapp/config/webpacker.yml (RuntimeError)
```

#### `webpacker.yml`を作成・編集

```terminal:ターミナル
touch config/webpacker.yml
```

```yml:webpacker.yml
# Note: You must restart bin/webpack-dev-server for changes to take effect

default: &default
  source_path: app/javascript
  source_entry_path: packs
  public_root_path: public
  public_output_path: packs
  cache_path: tmp/cache/webpacker
  webpack_compile_output: true

  # Additional paths webpack should lookup modules
  # ['app/assets', 'engine/foo/app/assets']
  additional_paths: []

  # Reload manifest.json on all requests so we reload latest compiled packs
  cache_manifest: false

  # Extract and emit a css file
  extract_css: true

  static_assets_extensions:
    - .jpg
    - .jpeg
    - .png
    - .gif
    - .tiff
    - .ico
    - .svg
    - .eot
    - .otf
    - .ttf
    - .woff
    - .woff2

  extensions:
    - .mjs
    - .js
    - .sass
    - .scss
    - .css
    - .module.sass
    - .module.scss
    - .module.css
    - .png
    - .svg
    - .gif
    - .jpeg
    - .jpg

development:
  <<: *default
  compile: true

  # Set to false if using HMR for CSS
  extract_css: true

  # Reference: https://webpack.js.org/configuration/dev-server/
  dev_server:
    https: false
    host: localhost
    port: 3035
    public: localhost:3035
    hmr: false
    # Inline should be set to true if using HMR
    inline: true
    overlay: true
    compress: true
    disable_host_check: true
    use_local_ip: false
    quiet: false
    pretty: false
    headers:
      'Access-Control-Allow-Origin': '*'
    watch_options:
      ignored: '**/node_modules/**'

test:
  <<: *default
  compile: true

  # Compile test packs to a separate directory
  public_output_path: packs-test

production:
  <<: *default

  # Production depends on precompilation of packs prior to booting for performance.
  compile: false

  # Cache manifest.json for performance
  cache_manifest: true
```

## 11.データベースを作成

```terminal:ターミナル
docker-compose run web rake db:create
```

### ※下記エラーが出る場合

```terminal:ターミナル
could not translate host name "db" to address: Name or service not known
```

```terminal:ターミナル
Error: Database is uninitialized and superuser password is not specified.
```

#### `docker-compose.yml`を編集

```yml:docker-compose.yml
# 変更前
environment:
  POSTGRES_PASSWORD: password

# 変更後
environment:
  - POSTGRES_HOST_AUTH_METHOD=trust
```