【Rails ＋ PostgreSQL ＋ Docker】既存のRailsアプリをDocker化する手順

Dockerとdocker-composeについて学習し既存のRailsアプリをDocker化したので、その手順をまとめたいと思います。完成版は[こちら](https://github.com/yokoyamamn/rails-docker)です。



# 前提
- Dockerの基礎知識については学習済みと仮定します。
- 今回使用する既存のRailsプロジェクト名は`rails-docker`になります。
- DBはpostgresのversion12を使用します。
- ホストのファイルシステムとコンテナのファイルシステムを同期させます。

# 手順
大まかに以下のようになります。
- `database.yml`の編集
- プロジェクト直下にDocker関係のファイル作成
- `Dockerfile`の編集
- `compose.yaml`の編集
- docker-composeで起動
- `localhost:3000/`にアクセスできるか確認

## `database.yml`の編集
以下のように記述します。`password`のところは環境変数を設定します。
```ruby
# PostgreSQL. Versions 9.3 and up are supported.
#
# Install the pg driver:
#   gem install pg
# On macOS with Homebrew:
#   gem install pg -- --with-pg-config=/usr/local/bin/pg_config
# On macOS with MacPorts:
#   gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
# On Windows:
#   gem install pg
#       Choose the win32 build.
#       Install PostgreSQL and put its /bin directory on your path.
#
# Configure Using Gemfile
# gem "pg"
#
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  user: postgres
  port: 5432
  password: <%= ENV.fetch("POSTGRES_PASSWORD") %>
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: myapp_development

  # The specified database role being used to connect to postgres.
  # To create additional roles in postgres see `$ createuser --help`.
  # When left blank, postgres will use the default role. This is
  # the same name as the operating system user running Rails.
  #username: myapp

  # The password associated with the postgres role (username).
  #password:

  # Connect on a TCP socket. Omitted by default since the client uses a
  # domain socket that doesn't need configuration. Windows does not have
  # domain sockets, so uncomment these lines.
  #host: localhost

  # The TCP port the server listens on. Defaults to 5432.
  # If your server runs on a different port number, change accordingly.
  #port: 5432

  # Schema search path. The server defaults to $user,public
  #schema_search_path: myapp,sharedapp,public

  # Minimum log levels, in increasing order:
  #   debug5, debug4, debug3, debug2, debug1,
  #   log, notice, warning, error, fatal, and panic
  # Defaults to warning.
  #min_messages: notice

# 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: myapp_test

# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password or a full connection URL as an environment
# variable when you boot the app. For example:
#
#   DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
#
# If the connection URL is provided in the special DATABASE_URL environment
# variable, Rails will automatically merge its configuration values on top of
# the values provided in this file. Alternatively, you can specify a connection
# URL environment variable explicitly:
#
#   production:
#     url: <%= ENV["MY_APP_DATABASE_URL"] %>
#
# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full overview on how database connection configuration can be specified.
#
production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV["MYAPP_DATABASE_PASSWORD"] %>
```

## プロジェクト直下にDocker関係のファイル作成
`Dockerfile`と`composeyaml`([docker dosc](https://docs.docker.com/compose/compose-file/03-compose-file/)によると現在は`docker-compose.yml`よりもこちらの方が推奨されているようです)を作成します。
```
rails-docker $ touch Dockerfile compose.yaml
```

## Dockerfileの編集

最終的に以下のようになりました。作成の過程を残すため、不要な行はコメントアウトしてます。`nodejs`と`yarn`はインストール不要みたいなのですが、この部分を消した場合の挙動は確認していないです。コメントにも書いていますが、`WORKDIR`は指定したフォルダがなければ自動で生成してくれます。`COPY . /rails-docker-app`の箇所は、ホストとコンテナを同期するように`compose.yaml`で記述するので不要だと認識しています。間違えていたらご教示いただきたいです。

```Dockerfile
FROM ruby:3.2.2
# Rails 7 では Webpacker が標準では組み込まれなくなった影響で yarn や Node.js のインストールが不要
# https://mseeeen.msen.jp/rails-docker/
RUN apt-get update && apt-get install -y nodejs postgresql-client yarn
# 指定したフォルダがなければ自動で生成されるので下記は不要
# RUN mkdir /rails-docker-app
WORKDIR /rails-docker-app
COPY Gemfile Gemfile.lock /rails-docker-app/
RUN bundle install
# COPY . /rails-docker-app
```

最初は何かの記事を参考に
```Dockerfile
COPY Gemfile Gemfile.lock /rails-docker-app/
```
の行を
```Dockerfile
COPY Gemfile Gemfile.lock /rails-docker-app/Gemfile/
```
としており、その状態で`docker build .`をすると以下のようにエラーになりました。

```
$ docker build .
[+] Building 105.6s (10/10) FINISHED                                                                                                
 => [internal] load .dockerignore                                                                                              0.2s
 => => transferring context: 2B                                                                                                0.0s
 => [internal] load build definition from Dockerfile                                                                           0.2s
 => => transferring dockerfile: 368B                                                                                           0.0s
 => [internal] load metadata for docker.io/library/ruby:3.2.2                                                                  5.1s
 => [auth] library/ruby:pull token for registry-1.docker.io                                                                    0.0s
 => [1/5] FROM docker.io/library/ruby:3.2.2@sha256:6a31af10aa94d7e5f6a4e2e45e8d96e070040a36e4f2cb9527c0f874b4ce2c97           73.0s
 => => resolve docker.io/library/ruby:3.2.2@sha256:6a31af10aa94d7e5f6a4e2e45e8d96e070040a36e4f2cb9527c0f874b4ce2c97            0.0s
 => => sha256:6a31af10aa94d7e5f6a4e2e45e8d96e070040a36e4f2cb9527c0f874b4ce2c97 1.86kB / 1.86kB                                 0.0s
 => => sha256:fd85f595c0b8dbfae34e0b27a56ae60a926e488049ab524a2cd7331d0a3173a7 8.16kB / 8.16kB                                 0.0s
 => => sha256:785ef8b9b236a5f027f33cae77513051704c0538bff455ff5548105c954c3b1c 49.56MB / 49.56MB                              16.2s
 => => sha256:5a6dad8f55ae6c733e986316bd08205c8b2c41640bf8d08ff6e9bbcb6884304f 24.03MB / 24.03MB                               4.1s
 => => sha256:af8ae0243e3283e06a65d2aa63fd3ed16d831ef5d06a89c1fd8d2b99f1d46782 1.79kB / 1.79kB                                 0.0s
 => => sha256:bd36c7bfe5f4bdffcc0bbb74b0fb38feb35c286ea58b5992617fb38b0c933603 64.11MB / 64.11MB                              30.5s
 => => sha256:4d207285f6d296b9806bd00340437406c25207412c52fcfcbf229a5ecff7bf94 211.03MB / 211.03MB                            36.6s
 => => sha256:db7d1c55965713c66c40e0cecf0c3aeb6f795937a3740aac7eac02a2dac97d62 200B / 200B                                    17.1s
 => => sha256:2c71b22972029a377f7c2fee19fdeae654e3cd4609137a8d8cc226f6e3584d12 34.69MB / 34.69MB                              30.6s
 => => extracting sha256:785ef8b9b236a5f027f33cae77513051704c0538bff455ff5548105c954c3b1c                                     18.5s
 => => sha256:c9adccf9b3c6473a6b3917e9ba46e829e83806fb12427d0a0c98fadbbd47f804 177B / 177B                                    31.7s
 => => extracting sha256:5a6dad8f55ae6c733e986316bd08205c8b2c41640bf8d08ff6e9bbcb6884304f                                      1.4s
 => => extracting sha256:bd36c7bfe5f4bdffcc0bbb74b0fb38feb35c286ea58b5992617fb38b0c933603                                      5.1s
 => => extracting sha256:4d207285f6d296b9806bd00340437406c25207412c52fcfcbf229a5ecff7bf94                                     23.6s
 => => extracting sha256:db7d1c55965713c66c40e0cecf0c3aeb6f795937a3740aac7eac02a2dac97d62                                      0.0s
 => => extracting sha256:2c71b22972029a377f7c2fee19fdeae654e3cd4609137a8d8cc226f6e3584d12                                      1.5s
 => => extracting sha256:c9adccf9b3c6473a6b3917e9ba46e829e83806fb12427d0a0c98fadbbd47f804                                      0.0s
 => [internal] load build context                                                                                              0.0s
 => => transferring context: 7.78kB                                                                                            0.0s
 => [2/5] RUN apt-get update && apt-get install -y nodejs postgresql-client yarn                                              25.7s
 => [3/5] WORKDIR /rails-docker-app                                                                                            0.1s
 => [4/5] COPY Gemfile Gemfile.lock /rails-docker-app/Gemfile/                                                                 0.1s
 => ERROR [5/5] RUN bundle install                                                                                             1.2s
------
 > [5/5] RUN bundle install:
#0 1.204 Could not locate Gemfile
------
Dockerfile:7
--------------------
   5 |     WORKDIR /rails-docker-app
   6 |     COPY Gemfile Gemfile.lock /rails-docker-app/Gemfile/
   7 | >>> RUN bundle install
   8 |     # COPY . /rails-docker-app
   9 |     
--------------------
ERROR: failed to solve: process "/bin/sh -c bundle install" did not complete successfully: exit code: 10
```

```Dockerfile
WORKDIR /rails-docker-app
```
で作業ファイルを指定しているのにGemfileフォルダにコピーしたために起こったエラーだと思われます。


## compose.yamlの編集
最終的に以下のようになりました。

```compose.yaml
version: "3.9"

volumes:
  db-data:

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - "POSTGRES_PASSWORD=postgres"
    tty: true
    stdin_open: true
    depends_on:
      - db
    links:
      - db
    volumes:
      - .:/rails-docker-app
    command: ["rails", "s", "-p", "3000", "-b", "0.0.0.0"]

  db:
    image: postgres:12
    volumes:
      - "db-data:/var/lib/postgresql/data"
    environment:
    - POSTGRES_DB=postgres
    - POSTGRES_USER=postgres
    - POSTGRES_PASSWORD=postgres
```

```compose.yaml
volumes:
      - .:/rails-docker-app
```
の行はホストとコンテナを同期させるために入れています。

```compose.yaml
environment:
    - POSTGRES_DB=postgres
    - POSTGRES_USER=postgres
    - POSTGRES_PASSWORD=postgres
```
の行は、ターミナルの実行結果が消えてしまったので詳しく書けないのですが、無しで実行するとDBのコンテナが作られなかったので、`参考`の動画などを見て記述しました。

```compose.yaml
command: ["rails", "s", "-p", "3000", "-b", "0.0.0.0"]
```
の行は、最初
```compose.yaml
command: ["bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"]
```
としていたのですが、最初の２つはなくても立ち上がりました。

## docker-composeで起動
いろいろ試行錯誤し、最終的に以下のように立ち上げました。dcはdocker-composeのことです(エイリアスを設定しています)。先述しましたがターミナルの実行結果が消えてしまったのでこれしか掲載できないです。すみません。

```
$ dc up -d --build
[+] Building 3.8s (11/11) FINISHED                                                                                                                    
 => [internal] load .dockerignore                                                                                                                0.1s
 => => transferring context: 2B                                                                                                                  0.0s
 => [internal] load build definition from Dockerfile                                                                                             0.1s
 => => transferring dockerfile: 530B                                                                                                             0.0s
 => [internal] load metadata for docker.io/library/ruby:3.2.2                                                                                    3.7s
 => [auth] library/ruby:pull token for registry-1.docker.io                                                                                      0.0s
 => [internal] load build context                                                                                                                0.0s
 => => transferring context: 61B                                                                                                                 0.0s
 => [1/5] FROM docker.io/library/ruby:3.2.2@sha256:6a31af10aa94d7e5f6a4e2e45e8d96e070040a36e4f2cb9527c0f874b4ce2c97                              0.0s
 => CACHED [2/5] RUN apt-get update && apt-get install -y nodejs postgresql-client yarn                                                          0.0s
 => CACHED [3/5] WORKDIR /rails-docker-app                                                                                                       0.0s
 => CACHED [4/5] COPY Gemfile Gemfile.lock /rails-docker-app/                                                                                    0.0s
 => CACHED [5/5] RUN bundle install                                                                                                              0.0s
 => exporting to image                                                                                                                           0.0s
 => => exporting layers                                                                                                                          0.0s
 => => writing image sha256:e94ddca34673f30ab6238bbbed22753d8b8154d94c6ac23af25cce0a34f3a8c7                                                     0.0s
 => => naming to docker.io/library/rails-docker-web                                                                                              0.0s
[+] Running 2/2
 ✔ Container rails-docker-db-1   Running                                                                                                         0.0s 
 ✔ Container rails-docker-web-1  Running 
```
問題なさそうです。

## `localhost:3000/`にアクセスできるか確認
以下にアクセスし、アプリが起動するか確認します。
http://localhost:3000/
画像のようになっていれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2207701/9b0e63b0-997d-3c9f-d3d5-70450859d1a3.png)
初めてアクセスした場合はデータベースがないというエラーが出るので、`rails db:create`をするかデータベースを作成するボタンが出てくるのでそれを押します(画像撮っとけばよかった)。


### 参考
- [米国AI開発者がゼロから教えるDocker講座 | Udemy](https://www.udemy.com/course/aidocker/)
- [クィックスタート: Compose と Rails | Docker ドキュメント](https://matsuand.github.io/docs.docker.jp.onthefly/samples/rails/)