初学者がDockerとdocker-composeでRailsアプリをDocker化してみた

## 1. 概要説明
この記事ではDockerとdocker-composeを使ってwebアプリをdocker化することを趣旨とした記事です。

利用環境は下記の通りです。
Mac Book Air M2チップ
Dockerのバージョン：24.0.7
docker-composeのバージョン：2.23.3-desktop.2
Rubyのバージョン：3.2.2
Railsのバージョン：7.0.6
Postgresql(DB)のバージョン：12

本記事は下記のサイトを参考にしています。
https://docs.docker.jp/compose/rails.html

## 2. Dockerとは
Dockerとはコンテナ型仮想環境用のプラットフォームです。
環境の仮想化は以前から可能でしたが、ハイパーバイザー型と呼ばれ、ホストマシン上に仮想ソフトをインストールしてその上にゲストOSと必要なアプリをインストールしていました。
この場合、アプリ毎にゲストOSが必要となり、物理マシン内を仮想化できても大量のリソース(容量)が必要になっていました。
対して、コンテナ型のDockerはホストOSの上にDockerを入れてしまえさえすれば、その上に直接アプリを動かすことができ、ハイパーバイザー型の様にアプリ毎のOSインストールで容量を沢山食うことにもなりません。

![スクリーンショット 2023-12-11 21.22.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3508918/481b2486-166a-3afd-8158-25e646e74a53.png)

## 2. Dockerfileの作成
Dockerfileとは構築するコンテナの内容を決める仕様書の様なものです。
Dockerfileを基にイメージが構築され、そのイメージを基にコンテナが作成されます。
以下は今回のWebアプリDocker化で実際に使用しているDockerfileの内容です。

```Dockerfile:Dockerfile
FROM ruby:3.2.2
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
ADD . /myapp
```

`ADD Gemfile /myapp/Gemfile`
`ADD Gemfile.lock /myapp/Gemfile.lock`
の行は`ADD Gemfile Gemfile.lock /myapp/`と一行にまとめることも可能

`RUN mkdir /myapp`と`WORKDIR /myapp`の行については
`mkdir`のコマンドを使ったらそのディレクトリに移動するので、`WORKDIR /myapp`を省くこともできる。

Dockerfile内の記述内容について説明します。
左端に書かれているFROM, RUNなどはインストラクションと呼ばれ、これらの指示を基にDockerのイメージが作成されます。
- FROM：Dockerfileの最初に書かれるインストラクションで、ここに書かれているイメージがベースとなってコンテナのイメージが作成されます。ここに書かれているイメージはDockerHubと呼ばれるイメージがストックされた場所から対象のベースとなるイメージが取得されます。
- RUN：Linuxコマンド等指定されたコマンドを実行します。&&を使えば複数のコマンドを一行にまとめられます。
&&を使わずに書くことも可能ですが、イメージ自体の容量がその分大きくなってしまうので、まとめられる部分はまとめましょう。
- WORKDIR：Linuxコマンドのpwdと似ており、現在いる場所を変更する時に使用します。
- ADD：ホストマシンのファイルをコンテナ内部にコピーしたり、圧縮されたファイルを解凍・展開する時に使用します。(今回は前者)

## 3. docker-composeとは
docker-composeとはDockerに付随する機能でコンテナを複数構築・起動する際に使用するツールです。
YAMLファイルを作成してコマンドを１つ実行するだけで瞬時にコンテナを立ち上げたりすることができます。
Dockerは１度に１つのコンテナしか操作できませんので、複数のコンテナが必要な場合はコンテナの数だけコマンドを実行したりする必要があり面倒です。
一方docker-composeであれば、YAMLファイルに各コンテナの内容を定義さえしてしまえば一回のコマンド操作で複数のコンテナが起動可能です。

## 4. docker-compose.ymlの作り方
docker-composeで必要なYAMLファイルを実際に説明していきます。
ファイル名としてはdocker-compose.ymlという名前で作成します。

```yml:docker-compose.yml
version: '3'
services:
  db:
    image: postgres:12
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
```

docker-compose.ymlの内容を説明すると下記の通りです。

- version：docker-composeのバージョンを定義します。
- services：定義するコンテナの名前をネストして(入れ子)記入します。今回の場合はdbとweb
- db：データベース用のコンテナに対して設けた名前
- image：イメージがストックされているDocker Hubからpostgresの12バージョンイメージを取得する
- environment；環境変数(PC内部の変数)を設ける。ここではDBのユーザー名とパスワード
- web：アプリ用のコンテナに対して設けた名前
- build：.(カレントディレクトリ)に存在するDockerfileを使ってコンテナを構築する
- command：コンテナを起動した際に実行するコマンド
- volumes：ホストマシンとコンテナ内部のフォルダを同期して、双方向からフォルダ内を行き来できるように設定。
これにより、コンテナ内部で作成したファイルを保管しておくことができます。
通常、コンテナを停止したらコンテナ内部で作成されてデータは削除されてしまうので、必要なファイルが有る場合は同期することでデータを保管できるようにします。
- ports：ホストマシン側とコンテナ側とでポートを指定して解放し、ホスト側からアクセスを可能とする
- depends_on：webとdbのコンテナ間で依存関係を作り、コンテナの起動順序を決める。
表示例の場合、dbが先に起動してwebはその後に起動する。停止する時は逆にdbが先に停止する。
これはDBコンテナが起動する前にWebコンテナが起動すると、Webコンテナ側が起動する時にDBに対して接続する設定処理がある場合、先にDBが起動していないとWebコンテナがエラーを出してしまう現象を避ける為に行います。

コンテナを構築するには基となるイメージを作成しますが、これには２通りのやり方が存在します。
１つはDockerfileを作成してコンテナの内容を定義する方法。表示例ではwebコンテナ側を指します。
２つ目はDocker Hubからイメージを取得してそれを基にコンテナを作成する方法。表示例ではdbコンテナを指します。

## 5. 実例
実際にDockerとdocker-composeを使ってWebアプリを立ち上げる方法を解説します。
ソフトなどのバージョンは最初の概要説明に記載しております。

必要なファイル
・Dockerfile (説明で使用した内容)
・docker-compose.yml (説明で使用した内容)
・Gemfile (インストールしたい内容が記載されている)
・Gemfile.lock(空のファイル)

GemfileではRailsのバージョンだけを指定した状態で作成します。
```gemfile:Gemfile
source 'https://rubygems.org'
gem 'rails', '7.0.6'
```

Dockerfileが存在するディレクトリに移動して下記のコマンドを実行します。

```docker:Rubyのファイル生成
docker-compose run web rails new . --force --database=postgresql
```

コマンドの内容を説明すると
- docker-compose run：docker-compose.ymlで指定したサービス一つを指定してコンテナを作成・起動
今回はwebを指定しています。
- rails new .：RubyのRailsというフレームワーク特有のコマンドで新しくアプリを作る際に必要なファイルを作成します。
- --force --database=postgresql：DBを強制的にPOSTGRESQLとして起動します。

上記のコマンドを実行したことにより、webのコンテナが起動されてrails new コマンドが実行され、アプリを新しく立ち上げる為に必要なファイルが生成されました。また、それに伴いGemfileとGemfile.lockも更新されています。
Gemfileはプログラミング言語の一つであるRubyを使う際に必要なファイルでここに記載されたGemがインストールされます。Gemとはパッケージ(ライブラリとも)のことで便利な機能を持ったプログラムの部品みたいなものです。
Gemfile.lockはGemfileに従ってインストールされた内容が記載されています。
これでアプリ起動に必要なデータが揃ったので、新しく下記のコマンドを起動します。

```docker:コンテナイメージ生成
docker-compose build
```
このコマンドはdocker-compose.ymlファイルを基にコンテナイメージを作成します。
rails new コマンドを実行したことによりアプリ起動に必要なGemfileなどが揃ったので、コンテナイメージを作成出来る段階にきました。

新しく生成されたファイルの中にdatabase.ymlというファイルがありますので、下記の内容を追記します。
```diff_yaml
default: &default
+  adapter: postgresql
  encoding: unicode
+  host: db
+  username: postgres
+  password: password
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
```
docker-compose.ymlで指定したusernameとpasswordで認証できるようにdatabase側でも同じ内容を登録します。
また、サービス名として登録した db はホスト名として使いますので、こちらもdatabase.ymlに登録します。
これによりホスト名を指定した通信が可能になります。

ここまでの準備ができたらさらに新しいコマンドで実際にwebアプリを起動します。

```docker:サービスの起動
docker-compose up
```
既に作成したコンテナイメージを基にコンテナを構築・起動します。

最後に以下のコマンドを起動すればwebアプリ起動が完了します。
`docker-compose up`を使った端末では入力できないので、別の端末を新しく立ち上げてそこから入力します。

```ruby:データベースの生成
docker-compose run web rake db:create
```

ブラウザで`localhost:3000`に接続すれば、railsのスタート画面が表示されるはずです。
    
データベースの生成をすることでwebアプリの起動を確認できます。

割とやってしまいがちなのが、rails new で新しいファイルを生成したりしてもコンテナイメージを新しく生成しようとせずにコンテナの構築を即座にやろうとしてしまうことです。
コンテナイメージに必要なファイルが増えたり、Dockerfileやdocker-compose.ymlなどに変更を加えた場合はコンテイメージを再度作り直す必要があります。これをやらずにコンテナを起動しようとすると古い内容のコンテナイメージを使ってコンテナを起動しようとするので、当然意図したコンテナの構築ができませんので注意してください。
`docker-compose build`でイメージを作り直すか、`docker-compose up --build`でイメージを作り直した後にコンテナを起動するようにコマンドを入力すれば変更が反映されたコンテナを構築することができます。
また、エラーが発生した時に全てDockerが原因でエラーが発生したと思いがちですが、Docker以外が原因でエラーが発生していることもあります。勿論、Dockerfileの書き方やdocker-compose.ymlの内容が間違っていれば、それはDocker関連が原因で止まっていることになりますが、今回の場合で言えばDockerの環境下でRubyやRailsを動かすことに特化した内容となっていますので、それらが原因で動かないケースも十分考えられます。エラーが出てしまってネットで検索する時の一助になればと思います。

ここまで読んで頂きありがとうございます。
実例の所では実際にwebアプリを起動するコマンドを紹介しましたが、実際にrailsのスタート画面を表示できたでしょうか？
もし表示できずにエラーが出てしまった方は是非そのエラーをヒントに解決し、railsの画面が表示できるように頑張ってください。
人によっては私と開発環境が異なる方もいらっしゃると思いますし、それによりエラーが発生する可能性もあります。私もこの記事を書くまでに色々なエラーと遭遇しましたが、何とかなりましたのできっとあなたも辿り着けると思います。