Dockerfileとdocker-composeでRailsとPostgreSQLを設定

# はじめに
Rails学習の一歩として、最初に取り組んだのはコンテナ環境の構築でした。予想以上に時間と労力がかかりましたが、何とか構築することができ、その結果、Dockerのスキルが大いに向上しました。環境構築やDockerスキルの向上を目指している方々に、少しでも参考になれば幸いです。Railsを学び始めた時の書籍が、「Rails5」「PostgreSQL10」を用いた環境のため、そこに焦点を当て環境構築を行いました。

# Dockerスキルアップのコツ
Dockerのスキルアップのコツは慣れです。同じコマンドや環境を何度も実行/構築することでスキルアップにつながります！暗記しようとせずに、慣れていきましょう！
また、これらはRailsやPostgresのバージョンが異なっていても、同様な考え方でコンテナ環境を構築できます。設定はバージョンごとに異なるかと思いますが、その都度調査することで、Dockerのスキルアップに繋がるため、ぜひチャレンジしてみてください！

# 準備
まずは、`「ローカル環境のフォルダ」` `「Dockerfile」` `「docker-compose.yml」` `「Gemfile」` `「Gemfile.lock」`を作成します。

## ローカル環境のフォルダ
handsonというフォルダを作成し、その中にDockerfile、docker-compose.yml、Gemfile、Gemfile.lockを作成して、それぞれ後述のコードをコピペします。

## Dockerfile

`Dockerfile`はDockerイメージのビルド手順を定義します。

1. `FROM ruby:2.5.1`
   - Ruby 2.5.1がインストールされたDockerイメージを指定します。
2. `RUN echo "deb http://archive.debian.org/debian/ stretch main" > /etc/apt/sources.list \ ...` 
   - Debianのパッケージリポジトリを設定し、build-essentialとnodejsをインストールします。build-essentialはCコンパイラなどのビルドに必要なパッケージをまとめたものです。nodejsはJavaScriptの実行環境です。
3. `RUN mkdir /app`
   - Dockerコンテナ内に/appというディレクトリを作成します。
4. `WORKDIR /app`
   - 作業ディレクトリを/appに変更して、以降の命令は/appから実行されます。
5. `COPY Gemfile /app/Gemfile`  `COPY Gemfile.lock /app/Gemfile.lock`
   - ホストのGemfileとGemfile.lockをコンテナの/appディレクトリにコピーします。
6. `RUN bundle install`
   - Gemfileに記載されたgemをインストールします。
7. `COPY . /app`
   - ホストの現在のディレクトリの全てのファイルをコンテナの/appディレクトリにコピーします。

```Dockerfile
FROM ruby:2.5.1
RUN echo "deb http://archive.debian.org/debian/ stretch main" > /etc/apt/sources.list \
    && echo "deb http://archive.debian.org/debian-security stretch/updates main" >> /etc/apt/sources.list \
    && apt-get update -qq && apt-get install -y build-essential nodejs
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app
```

## docker-compose.yml

`docker-compose.yml`は複数のコンテナ間の関係を定義します。

1. `version: '3'` 
   - docker-composeのバージョンを指定しています。
2. `services:`
   - webとdbの2つのサービス（コンテナ）を定義しています。
3. `web:`
   - webサービス（コンテナ）の設定を始めることを示します。
   - `build: .` 
     - 現在のディレクトリ（.）にあるDockerfileに基づいてDockerイメージをビルドします。
   - `command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"`
     - コンテナが起動する際に実行されるコマンドを指定します。ここでは、まず既存のserver.pidファイルを削除し（存在すれば）、その後Railsサーバを起動します。
   - `volumes: - .:/app`
     - ホストの現在のディレクトリ（.）とコンテナ内の/appディレクトリをマウントします。ホストとコンテナ間でファイルが共有されます。
   - `ports: - 3000:3000`
     - ホストの3000番ポートとコンテナの3000番ポートをマッピングします。ホストのブラウザからhttp://localhost:3000でRailsアプリケーションにアクセスできます。
   - `depends_on: - db`
     - webサービスがdbサービスに依存していることを示します。dbサービスが先に起動した後でwebサービスが起動します。
   - `tty: trueとstdin_open: true`
     - これらのオプションはコンテナ内での対話型プロセスを可能にします。
4. `db:`
   - dbサービス（コンテナ）の設定を始めることを示します。
   - `image: postgres:10.1`
     - バージョン10.1のPostgreSQLイメージ（Dockerイメージ）を使用します。
   - `volumes: - postgres_volume:/var/lib/postgresql/data`
     - 名前付きボリュームpostgres_volumeとコンテナ内の/var/lib/postgresql/dataディレクトリをマウントすることで、データベースのデータが永続化されます。
   - `environment: POSTGRES_PASSWORD: password`
     - 環境変数を設定します。ここではPostgreSQLのパスワードをpasswordとしています。
   - `ports: - 5432:5432`
     - ホストの5432番ポートとコンテナの5432番ポートをマッピングします。
   - `restart: always`
     - コンテナが何らかの理由で終了した場合、Dockerが自動的にコンテナを再起動します。
5. `volumes: postgres_volume:`
   - 名前付きボリュームpostgres_volumeの設定を始めることを示します。

```yaml
version: '3'
services:
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - 3000:3000
    depends_on:
      - db
    tty: true
    stdin_open: true
  db:
    image: postgres:10.1
    volumes:
      - postgres_volume:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
    restart: always
volumes:
  postgres_volume:
```

## Gemfile

GemfileはRubyのプロジェクトで使用するgem（ライブラリ）を管理するためのファイルです。
1. `source 'https://rubygems.org'` gemの取得元となるソースを指定しています。ここでは公式のRubyGemsリポジトリからgemを取得します。
2. `gem 'rails', '~> 5.2.0'` rails gemのバージョンを5.2.xと指定しています。この記述により、bundle installコマンドを実行したときにRailsのバージョン5.2.xがインストールされます。
```bash
source 'https://rubygems.org'
gem 'rails', '~> 5.2.0'
```

## Gemfile.lock
Gemfile.lockはRubyのプロジェクトで使われるライブラリ（gem）のバージョンを記録するファイルです。Gemfile.lockがあることで、同じプロジェクトでも異なる環境で実行したときに同じgemのバージョンが使われ、挙動の違いを防げます。ただし、新しいRailsプロジェクトを作成する際には、まだどのgemを使用するか、それらのバージョンが決まっていないため、`Gemfile.lockは空`にします。その後、bundle installが初めて実行されるとき、Gemfile.lockにはGemfileに記述されたgemのバージョンが自動的に記録されます。

# docker-composeコマンド
`docker-compose`を実行して、Rails5のコンテナ環境を構築します。

はじめに、`docker-compose run`コマンドを実行して、Railsアプリケーションの初期セットアップを行います。
- `web` docker-compose.yml ファイル内のサービス名を指しています。
- `rails new .` 新しいRailsアプリケーションを現在のディレクトリ（.が現在のディレクトリを示します）に生成するためのコマンドです。
- `--force` ファイルがすでに存在していた場合でも、強制的に新しいRailsアプリケーションを生成するためのオプションです。つまり、すでにあるファイルは新しいもので上書きされます。
- `--database=postgresql` 新しいRailsアプリケーションのデータベースとしてPostgreSQLを使用するように設定するためのオプションです。
```bash
docker-compose run web rails new . --force --database=postgresql
```

`docker-compose build`コマンドを実行して、Dockerfileからイメージをビルドします。
```bash
docker-compose build
```

`docker-compose up -d`コマンドを実行して、コンテナをバックグラウンド（-dオプション）で起動します。
```bash
docker-compose up -d
```

この段階ではまだエラーになるため、データベースの設定を行います。
![スクリーンショット 2023-06-11 11.08.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263017/51ce2f61-a712-04c4-f9a2-103c8295e3b0.png)


Railsのconfig/database.ymlを編集します。
```yml
default: &default
~~~~省略~~~~~~
~~~~以下の3行を追加~~~~~~
  host: db
  username: postgres
  password: password
~~~~~~~~~~
```

`docker-compose run`コマンドを実行して、データベースを作成します。
```bash
docker-compose run web rails db:create
```

`docker-compose restart`コマンドを実行して、コンテナを再起動します。
```bash
docker-compose restart
```

localhost:3000にアクセスして、Railsのデフォルトページが表示されれば成功です。
![スクリーンショット 2023-06-11 10.16.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263017/9115cef4-0df1-a623-5ee4-f442c72df2d5.png)

`docker-compose stop`コマンドを実行して、コンテナを停止します。
```bash
docker-compose stop
```

localhost:3000にアクセスして、表示されないことを確認します。
![スクリーンショット 2023-06-11 10.18.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263017/aaee3349-0921-ac35-874a-7d5d36aa63bf.png)


# 不要なコンテナやイメージを削除する
これまでの操作を行なっていくと、「dockerコンテナ」「dockerイメージ」が複数作成されているかと思います。

```bash
handson % docker-compose ps -a
NAME                           IMAGE                                                                     COMMAND                  SERVICE             CREATED             STATUS                          PORTS
handson-db-1                   postgres:10.1                                                             "docker-entrypoint.s…"   db                  23 minutes ago      Exited (0) 10 seconds ago
handson-web-1                  handson-web                                                               "bash -c 'rm -f tmp/…"   web                 10 minutes ago      Exited (1) 10 seconds ago
handson-web-run-7188bd29e0ec   handson-web                                                               "rails db:create"        web                 2 minutes ago       Exited (0) About a minute ago
handson-web-run-eb881b1d471e   sha256:6ff7a599ebf79862b6f97c4c8af0f53c8422400632514855d24ae26ab637714c   "rails new . --force…"   web                 23 minutes ago      Exited (0) 22 minutes ago

handson % docker-compose images
CONTAINER                      REPOSITORY          TAG                 IMAGE ID            SIZE
handson-db-1                   postgres            10.1                ec61d13c8566        287MB
handson-web-1                  handson-web         latest              8e1e773be953        1GB
handson-web-run-7188bd29e0ec   handson-web         latest              8e1e773be953        1GB
handson-web-run-eb881b1d471e   <none>              <none>              6ff7a599ebf7        972MB
```

`docker-compose up -d`を実行したときに作成されるコンテナは以下の2つです：

`handson-web-1：`Railsアプリケーションをホストするコンテナで、Dockerfileからビルドされたイメージruby:2.5.1を使用します。docker-compose.ymlファイルのwebサービスに対応しています。

`handson-db-1：`PostgreSQLデータベースをホストするコンテナで、公式のPostgreSQLイメージpostgres:10.1を使用します。docker-compose.ymlファイルのdbサービスに対応しています。

これらの2つのコンテナが起動しているため、Railsアプリケーションはブラウザからアクセス可能になっています。
Railsアプリケーションがブラウザからアクセス可能になるのは、rails1-web-1とrails1-db-1という2つのコンテナが起動しているからです。その他のコンテナやイメージは一時的に作成されたもので、Railsアプリケーションの動作には直接影響しません。

そのため、以下のコマンドでコンテナ、イメージを削除します。

## コンテナを削除
不要なコンテナを削除する。
```bash
docker rm handson-web-run-7188bd29e0ec
```

## イメージを削除
不要なイメージを削除する。
```bash
docker rmi 6ff7a599ebf7
```

## 全てのリソースを削除
全ての作業が完了し、利用が終わり、不要になった場合は、`docker-compose down --rmi all --volumes --remove-orphans`コマンドを実行して、コンテナ、ネットワーク、イメージ、ボリュームを削除できます。
```bash
docker-compose down --rmi all --volumes --remove-orphans
```

# まとめ
環境構築の流れをまとめました。
```bash
# コンテナ作成の前準備
「ローカル環境のフォルダ」 「Dockerfile」 「docker-compose.yml」 「Gemfile」 「Gemfile.lock」を作成

# Railsアプリケーションのコンテナ環境を構築
docker-compose run web rails new . --force --database=postgresql
docker-compose build
docker-compose up -d
~~ database.ymlを編集 ~~
docker-compose run web rails db:create
docker-compose restart
docker-compose stop
docker-compose down --rmi all --volumes --remove-orphans

# 不要なコンテナ、イメージを削除したい場合
docker rm ${コンテナ}
docker rmi ${イメージ}
```