RailsプロジェクトをDocker化してみた

# はじめに
テスト的に作られたRailsアプリをDocker化する経験をさせて頂いたので、そこで得られた知見を皆さんに共有させていただきたいと思います。

完全に新しくDockerで環境を構築するのとは似ていますが、多少違いがありましたのでそこら辺の微妙な違いについても説明しています。
### この記事で参考になること
- nodejsのマルチステージビルドのやり方
- .envの環境変数をdocker-compose.ymlで読み込み、Dockerfileに変数を渡す
- rails プロジェクトでpostgresqlを使用する方法
- コンテナ間通信のやり方
- 新しい環境をDockerで構築する流れとの違い

※Dockerについて逐一説明する記事ではなくTips的な記事であることを先にご了承ください

さっそく本題に入っていきましょう！

# RailsプロジェクトをDocker化

## フォルダ構成

普通のRailsプロジェクトのルートに
- Dockerfile
- docker-compose.yml
- .env

ファイルを今回は追加しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3350234/8dbdbeaf-ee73-9345-e699-da42ca537a58.png)

## .envファイルについて

```:.env
YARN_VERSION=1.22.15
POSTGRES_PASSWORD=postgres
```
今回はnodeとyarnをマルチステージビルドでインストールを行ったので、その際にyarnのバージョン情報が必要だったため環境変数にYARN_VERSIONを入れています。

POSTGRES_PASSWORDについてはpostgresqlのイメージを使いたい場合は必ず設定しないといけない項目です。

詳しく知りたい方は[ポスグレの公式DockerImage](https://hub.docker.com/_/postgres)のoverviewのhow to useを見てください

:::note info
追記
レビューしていただいた結果開発環境では.envで変数を渡すのは冗長なのでdocker-compose.ymlに直接書いて大丈夫だということが分かりました。

今回は、せっかく.envで作成したのでやり方を含めて解説していますが、開発環境を構築する際は直接書くようにするとgoodだと思います！
:::
勝又さんもそういっていました。

https://twitter.com/poly_soft/status/1508709961182101508

## Dockerfileについて
```Dockerfile:Dockerfile
FROM node:14.18.1-slim AS node

FROM ruby:3.0.2-slim

ARG YARN_VERSION

RUN mkdir -p /opt
COPY --from=node /opt/yarn-v${YARN_VERSION} /opt/yarn
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 -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
  && ln -s /opt/yarn/bin/yarn /usr/local/bin/yarnpkg \
  && ln -s /usr/local/bin/node /usr/local/bin/nodejs \
  && ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
  && ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npx \
  && apt-get update \
  && apt-get install -y \
     g++ make \
     libpq-dev \
     postgresql-client

WORKDIR /app

COPY Gemfile Gemfile.lock package.json /app/

RUN bundle install && yarn

CMD ["rails", "s", "-b", "0.0.0.0"]
```

### POINT1 マルチステージビルド
node イメージにはrails環境を作る際に不要なパッケージなどが含まれているので、

本当に必要最低限のnodeとyarnをrubyコンテナにコピーしています。

必要なものだけをインストールする際は大体
```
/usr/local/bin/パッケージ名
/usr/local/lib/
```
あたりを目をつけてコピーすれば大丈夫です。

/usr/local/includeや/usr/local/shareなどは使い方の参考例が書いてあるファイルだったり、nodejsの実行環境のためのファイルだったりする感じなので、今回はyarnでライブラリを管理したいだけなのでコピーしなくてOKです。

[nodeマルチステージビルド参考](https://tech.actindi.net/2019/06/20/084247)

### POINT2 docker-compose.ymlから値を受け取る
```Dockerfile:Dockerfile
ARG YARN_VERSION
```
この部分でdocker-compose.ymlのargs(後述)から渡されてきた変数を受け取り
```Dockerfile:Dockerfile
COPY --from=node /opt/yarn-v${YARN_VERSION} /opt/yarn
```
Dockerfile内で${変数名}とすることによって変数の中身が展開されます。


### POINT3 バージョンの指定
今回採用したnode imageを見てみると14.18.1を使用しています。

最新のnodeバージョンを使用すると

```
webpacker::manifest::missingentryerror
```

というエラーがrails s -b 0.0.0.0をした際に出たのあえてバージョンをかなり下げています。


ruby:3.0.2もバージョンが指定されていますが、これは既存のプロジェクトのGemfileに記載されている

rubyのバージョンに合わせる必要があるのでそこも気を付けてください


## docker-compose.ymlについて
```yml:docker-compose.yml
version: '3'

services:
  web:
    build: 
      context: .
      args:
        YARN_VERSION: ${YARN_VERSION}
    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    ports:
      - '3000:3000'
    volumes:
      - '.:/app'
    tty: true
    stdin_open: true
    depends_on:
      - db
    links: 
      - db

  db:
    image: postgres:12
    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - 'db-data:/var/lib/postgresql/data'

volumes:
  db-data:
```

### POINT1.envから変数を受け取り変数をDockrefileに渡す
```yml:docker-compose.yml
    build: 
      context: .
      args:
        YARN_VERSION: ${YARN_VERSION}
```

docker-compose.ymlは同階層にある.envの変数を自動的に読み込んでくれます。

~~つまりdocker-compose.ymlに何か変数を渡したかったら.envにすべて書けばOKです。~~
.envには秘匿情報・環境(dev,test,production)に依存する変数のみ書くようにしましょう

使い方はDockerfileと同様${変数名}とすれば展開されます。


またDockrefileに変数を渡したいときは

上記のようにargsを指定してやれば先ほどDockerfileのpoint 2で解説した受け取り方で受け取れます。

### POINT2コンテナ間通信について
[docker-composeのネットワーク](https://docs.docker.jp/compose/networking.html)

>デフォルトで Compose はアプリに対して１つの ネットワーク を作成します。サービス用の各コンテナはデフォルトのネットワークに接続し、そのネットワーク上で他のコンテナと相互に「 接続可能reachable 」になります。そして、コンテナ名と同じホスト名として、お互いが「 発見可能discoverable 」になります。


```yml:docker-compose.yml
links: 
  - db
```
linksを書かなければホスト名として名前を解決することはできますが実際に接続ができるようにはならないので注意


```yml:docker-compose.yml
depends_on:
  - db
```
depends_onを指定することで、DBコンテナを作成・実行してからwebコンテナが立ち上がるようになります。

もしDBコンテナが立ち上がっていないのに、WebコンテナがDBコンテナにアクセスしようとしたらエラーになってしまいます。

## database.ymlとGemfile
```yml:database.yml
default: &default
  adapter: postgresql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
  encoding: unicode
  host: db
  user: postgres
  port: 5432
  password: <%= ENV.fetch("POSTGRES_PASSWORD") %>

development:
  <<: *default
  database: db_dev

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: dev_test

production:
  <<: *default
  database: db_prod

```

***adapter***：postgresqlに変更

***host***：今回は外部コンテナにDBがある構成なので、その場合はdocker-compose.ymlのサービス名を記載してあげれば勝手に名前解決してくれます。(めっちゃ便利)


***password***：docker-compose.ymlで環境変数に設定したPOSTGRES_PASSWORDを展開する



```:Gemfile
# Use postgres as the database for Active Record
gem 'pg'
```
postgreを使用するために必要なgemです。自分のプロジェクトにあったバージョンを使用しましょう。

またsqliteなどいらなくなったDB用のgemがあったら削除しましょう。


ファイル編集などの必要な変更は以上です。

必要に応じてdb:createなどを行ってください。
# 最後に



個人的にマルチステージビルドの部分とバージョンをエラーが出たら逐一動くようになるバージョンに変更する作業が結構大変だったと思います。

しかし一回慣れてしまえばdockerは怖くなくなったので、これから開発を行う時は全部docker化していく。

みなさんが既存のプロジェクトでdockerを使用する際の雰囲気をつかんでいただけたら幸いです。