Rails+PostgreSQLアプリをDocker化する手順

# RailsアプリをDocker化する手順
既存のRailsアプリをDocker上で動かせるように環境構築を行います。

1. Railsアプリを用意する
2. Dockerfileを作成する
3. docker-compose.ymlを作成する
4. Gemfileを編集する
5. config/database.ymlを編集する
6. Dockerイメージをビルドする
7. Dockerコンテナを作成し起動する
8. webコンテナに対してデータベースを作成するコマンドを実行する
9. http://localhost:3000 にアクセスして表示を確認する

## Railsアプリを用意する
使用するバージョンは以下の通り。
- ruby 3.0.2
- rails 6.0.3
- gem 3.2.22
- bundler 2.2.22
- node 12.22.12
- npm 6.14.16
- yarn 1.22.19
- PostgreSQL 13.9

## Dockerの基本的な使い方
Dockerは基本的に以下の流れで環境構築を行う。

1. Docker Hubからイメージを取得する、またはDockerfileでイメージをビルドする
2. イメージからコンテナを作成し起動する
3. コンテナで任意の操作を行う

## Dockerfileを作成する
Dockerfileを使ってコンテナの元となるイメージを作成する。

Dockerfileの内容は以下の通り。

```
# ベースイメージを指定する
FROM node:12 as node
FROM ruby:3.0.2

# ワーキングディレクトリを指定する
WORKDIR /src

# マルチステージビルドでrubyコンテナにnodeとnpmを追加する
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules/ /usr/local/lib/node_modules/

RUN ln -fs /usr/local/bin/node /usr/local/bin/nodejs \
    && ln -fs /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
    && ln -fs /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npx

# aptを更新し、必要なパッケージをインストールする
RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    postgresql-client

# ホストのGemfileとGemfile.lockをコンテナの/src/にコピーする
COPY Gemfile Gemfile.lock /src/

# Gemをインストールする
RUN bundle install

# ホストのpackage.jsonとyarn.lockをコンテナ内にコピーする
COPY package.json yarn.lock /src/

# npmを使ってyarnをグローバルインストールする
RUN npm install --global yarn

# yarnを更新する
RUN yarn install
```

### ベースイメージを指定する
```
FROM node:12 as node
FROM ruby:3.0.2
```

`From`は使用するイメージを指定する。

Rubyとnodeのイメージを使う。イメージ名の後ろはバージョンを指定する。`as`を使ってイメージ名を変更できる。

基本的にDockerfileは1つのファイルにつき1つのイメージだが、ここではマルチステージビルドを使ってnodeをインストールする。

マルチステージビルドによって複数のイメージを同時にビルドでき、イメージの容量を小さくできる。

### ワークディレクトリを指定する
```
WORKDIR /src
```

`WORKDIR`はDockerfileの内容を実行するディレクトリを指定する命令。

### マルチステージビルドでrubyコンテナにnodeとnpmを追加する
マルチステージビルドを行うために、nodeとnpmのバイナリファイルをコピーして、シンボリックリンクを貼る。

```
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules/ /usr/local/lib/node_modules/
```

`COPY`はホストのファイルをコンテナ内にコピーする命令。

コマンドの最後にある`\`はコマンドを改行するための文字で、2行目と3行目にある`&&`はコマンドをつなげて同時に実行するための文字。

```
RUN ln -fs /usr/local/bin/node /usr/local/bin/nodejs \
    && ln -fs /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
    && ln -fs /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npx
```

`RUN`は後ろに記述したコマンドをコンテナ内で実行する命令。

`ln`はファイルのリンクを作成するLinuxのコマンドで、`-fs`オプションでシンボリックリンクを作成する。


### aptを更新し、必要なパッケージをインストールする
```
RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    postgresql-client
```

`apt`はDebian系のLinuxディストリビューションのパッケージ管理システムのことで、`apt-get`でコマンドを実行できる。

`apt-get update`でaptを更新する。

`apt-get install -y`でパッケージをインストールする。`-y`のオプションをつけることで、インストール時に聞かれる質問を飛ばすことができる。

`build-essential`は開発に必須のビルドツールを提供しているLinuxのパッケージ。

`libpq-dev`はPostgreSQLクライアントのヘッダやライブラリを含むパッケージで、PostgreSQLを使うために必要になる。

`postgresql-client`はPostgreSQLのクライアントアプリケーションであるpsqlを使えるようにするためのパッケージ。


### ホストのGemfileとGemfile.lockをコンテナの/src/にコピーする
```
COPY Gemfile Gemfile.lock /src/
```

`COPY`はホストのファイルをコンテナにコピーする命令。ホストとは自分のPCのこと。ここではGemfileとGemfile.lockをコピーする。

`Gemfile`はRubyのパッケージ管理システムである`Gem`を使うためのファイル。RailsもGemを使ってインストールする。

`Gemfile.lock`はインストールされているパッケージとそのバージョンが記述されているファイル。

既存のRailsアプリなので、GemfileとGemfile.lockはすでに用意されている。


### Gemをインストールする
```
RUN bundle install
```

`bundle install`によって、コンテナ側でコピーしたホストのGemfileの内容を元にパッケージをインストールする。

`bundle`はgemを管理するライブラリのこと。bundleを使うことで、gemの依存関係や互換性を管理してくれ、gemのパッケージを一括でインストールしたりできる。


### ホストのpackage.jsonとyarn.lockをコンテナ内にコピーする
```
COPY package.json yarn.lock /src/
```

`package.json`はnpmのパッケージ名が記述されたファイル。npmとはNode.jsのパッケージ管理システムのこと。

`yarn.lock`はyarnのlockファイル。yarnとはnpm同様、Node.jsのパッケージ管理システムで、npmより動作が速い。
yarnではなくnpmを使ってもいい。npmの場合はpackage.lock.jsonを使う。

### npmを使ってyarnをグローバルインストールする
```
RUN npm install --global yarn
```

npmを使ってyarnをグローバルインストールする。


### yarnを更新する
```
RUN yarn install
```

`yarn install`によってコピーしたpackage.jsonの内容でインストールされる。




## docker-compose.ymlを作成する
Docker Composeを使用するためにdocker-compose.ymlを作成する。今回はDockerfileと同じディレクトリに作成する。

Docker Composeを使えば、複数のコンテナを同時に操作でき、またコンテナ同士で通信を行うためのネットワークを自動で作成できる。

docker-comopose.ymlの内容は以下の通り。
```
# Docker Composeのバージョンを指定する
version: '3'

# 各コンテナを指定する
services:

  # webコンテナ
  web:
    # コマンドを実行するディレクトリにあるDockerfileを使ってビルドする
    build: .

    # ホストの3000番ポートとコンテナの3000番ポートを紐づける
    ports:
      - '3000:3000'

    # コンテナを起動した時にrails serverを起動する
    command: bundle exec rails s -p 3000 -b '0.0.0.0' 

    # docker-compose.ymlと同じディレクトリにあるファイルやフォルダをコンテナの/src配下にマウントする（ホストでの変更がコンテナに反映されるようにする）
    volumes:
      - .:/src

    # dbコンテナを起動してからwebを起動する
    depends_on:
      - db
    
  # dbコンテナ
  db:
    # Docker Hubのイメージを使う
    image: postgres

    # コンテナに環境変数を設定する
    environment:
      - POSTGRES_PASSWORD=password
```


## Gemfileを編集する
railsでPostgreSQLを使うために、Gemfileに以下のコードを追記する。

```
gem 'pg', '~> 0.18.4'
```

## config/database.ymlを編集する
Railsアプリを作成するとデータベースの設定を行う`config/database.yml`が作られており、デフォルトでは`sqlite`になっているのでこれを`postgresql`に変更する。

database.ymlの内容は以下の通り。
```
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: rails-docker_development

test:
  <<: *default
  database: rails-docker_test

production:
  <<: *default
  database: rails-docker_production
```

## イメージをビルドする
Docker Composeを使ってイメージをビルドする。

```
docker compose build
```

## コンテナを作成し起動する
ビルドしたイメージを使ってコンテナを作成し起動する。

```
docker compose up -d
```

## webコンテナでデータベースを作成する
起動したwebコンテナでrailsアプリのデータベースを作成する。

```
docker compose exec web rails db:create db:migrate
```

`docker compose exec`コマンドは起動しているコンテナに対して後の命令を実行する。ここではwebコンテナに対してコマンドを実行する。

`exec`の後ろはコンテナの名前を指定する。コンテナの名前はdocker-compose.ymlを使っていれば`services`に記述した名前を使う。


`web`以降のコマンドはrailsのコマンドを実行している。`db:create`でデータベースを作成し、`db:migrate`でデータベースのテーブルを作成する。


## Railsのページを確認する
ここまでの操作がうまく実行できていれば、http://localhost:3000 にアクセスするとRailsのページが表示される。

3000というのはdocker-compose.yml内で記述したポート番号。もし違うアプリで3000番を使っている場合は他の番号を指定する。


## ※webコンテナがExitedになってしまう場合
webコンテナが起動してもすぐExitedになってしまうエラーが発生。

エラーメッセージ
```
/usr/local/bundle/gems/webpacker-5.4.4/lib/webpacker/configuration.rb:103:in `rescue in load': Webpacker configuration file not found /src/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /src/config/webpacker.yml (RuntimeError)
```


とりあえず`config/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: false

  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

  # 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

  # Extract and emit a css file
  extract_css: true

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

うまく起動した。

webpackerはrailsのフロントの部分を便利に書けるもので、rails6では標準になっているらしい。

## 参考
https://qiita.com/NaokiIshimura/items/8203f74f8dfd5f6b87a0