DockerでRailsの環境構築してHerokuへデプロイする

# はじめに
3週間くらい前に初めてDockerに触れて、その時のメモを放置したままだったので、改めてまとめました。

Dockerに初めて触れた時に練習としてやった超簡単な環境構築の話なので、私のように、Docker触ったことない・これから触ってみたいという人を対象とした記事になっています。

また、Dockerへの理解はまだまだ浅いこともあるため、間違った部分があったり、非効率的な導入方法になっているかもしれない点はご了承ください！(アドバイス等あれば是非ご教示いただければ幸いです！)

# 前提
- MacOS
- Ruby, Rails, PostgreSQLなど、最新版を使用

# Dockerとは？
詳細は割愛します。「Dockerとは」等でググったら出てくるので今回はそちらにお任せしたいと思います。

個人的には以下の記事が初心者的には分かりやすいと思いました。
> [DockerでRuby on Railsの開発をしよう](https://qiita.com/saitoeku3/items/b1aa2ae143624e551aea)

また、ドットインストールで概要だけでも知るのもいいかもしれません(少し公開日が古いです)。

> [ドットインストール Docker入門 (全11回)](https://dotinstall.com/lessons/basic_docker)

個人的に感じたメリットとしては、Dockerで環境構築して設定すれば、PCが変わってもそのPCにDockerがあればすぐに同じ環境を用意できる点などで、一度用意しておけば環境構築に時間を取られなくなるのは魅力的だなと思いました。

# DockerでRailsの環境構築してHerokuへデプロイするまでの流れ

## Dockerのインストール
[DockerHub](https://hub.docker.com/)に登録して、Macユーザーの場合は[Docker for Mac](https://docs.docker.com/docker-for-mac/install/)をインストールします。

これ以上特筆すべき点はないと思うので次に進みます。

## Docker基本コマンド
[Dockerコマンド一覧](https://qiita.com/nimusukeroku/items/72bc48a8569a954c7aa2)や[DockerでRuby on Railsの開発をしよう](https://qiita.com/saitoeku3/items/b1aa2ae143624e551aea)に載っているのでここでは割愛します。

## DockerでRuby/Railsの環境構築
- 以下を参考にしながら進めました

- [Quickstart: Compose and Rails](https://docs.docker.com/compose/rails/)
- [docker-composeでRailsを開発しCircleCI 2.0でテストしてHerokuにdeployする](https://qiita.com/Kesin11/items/47079bc7f659e71b694c)

好きなディレクトリに移動して作業

```zsh:
% cd /Users/fuku_tech/works/myapp
```

Dockerfileを作成

```zsh:
% vim Dockerfile
または
% touch Dockerfile
```

Dockerfileの中を記述

```Docker:Dockerfile
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
```

次にGemfileとGemfile.lockを作成
初めて触る時は何でGemfileとかここで作るん？ってなりましたが、気にせず作って下さい。

```zsh:
% touch Gemfile && touch Gemfile.lock
```

そしてGemfileの中身を以下の通り記述

```ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '~>5'
```

続いて`docker-compose.yml`を作成＆中身を追加
[こちら](https://qiita.com/Kesin11/items/47079bc7f659e71b694c#docker-composeyml)を参考に作りました。

```yml:docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    ports:
      - '5432:5432'
    volumes:
      - postgresql-data:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  postgresql-data:
    driver: local
```

[公式](https://docs.docker.com/compose/rails/)に従って`entrypoint.sh`を同ディレクトリに追加

```
% touch entrypoint.sh
```


以下のシェルスクリプトを追加したら、`server.pid`ファイルがあるときにサーバーが再起動するのを妨げてしまうというRails特有の問題を解消してくれるとのこと。

```bash:entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```

そしてdocker上で`rails new`するために以下のコマンドを実行

```zsh:
% docker-compose run web rails new . --force --no-deps --database=postgresql
```

`ls`コマンドで確認したら沢山ファイルが生成されてます

```zsh:
% ls
Dockerfile              config/                 package.json
Gemfile                 config.ru               public/
Gemfile.lock            db/                     storage/
README.md               docker-compose.yml      test/
Rakefile                entrypoint.sh           tmp/
app/                    lib/                    vendor/
bin/                    log/
```

イメージをbuildして

```zsh:
% docker-compose build
```

`database.yml`を設定

```yml:config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: 5

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>
```

コンテナを作成＆起動でrailsサーバーが立ち上がります。

```zsh:
% docker-compose up

# psコマンドで起動確認
% docker-compose ps
   Name                  Command               State           Ports         
-----------------------------------------------------------------------------
myapp_db_1    docker-entrypoint.sh postgres    Up      0.0.0.0:5432->5432/tcp
myapp_web_1   entrypoint.sh bash -c rm - ...   Up      0.0.0.0:3000->3000/tcp
```

最後にローカル環境のDBを作成＆migrate

```zsh:
# DB作成
% docker-compose run web rake db:create
# migrate実行
% docker-compose run web rails db:migrate
```

ブラウザで`localhost:3000`にアクセスするとrailsの初期画面が出現

![image.png](https://qiita-image-store.s3.amazonaws.com/0/307533/b8d26554-1db3-56c2-e3f8-729711776aa1.png)

## Herokuへデプロイ
下記リンクを参考にしながら実施しました。

> [docker-composeでRailsを開発しCircleCI 2.0でテストしてHerokuにdeployする](https://qiita.com/Kesin11/items/47079bc7f659e71b694c)
> [Container Registry & Runtime (Docker Deploys)](https://devcenter.heroku.com/articles/container-registry-and-runtime#getting-started)

```
# herokuのコンテナレジストリにログイン
heroku container:login

# 新しいappを作成
heroku create

# イメージを作成してコンテナレジストリにpush
heroku container:push web

# postgresqlアドオンの無料プランを追加
heroku addons:create heroku-postgresql:hobby-dev

# DBセットアップ
heroku run rails db:migrate

# イメージをherokuへデプロイ
heroku container:release web

# 実際にアクセスして/usersを確認してみる
heroku open
```

# まとめ
- 遂に、ずっと気になっていたDockerに着手できました。
- 導入自体は滞りなくすることができましたが、今後、CircleCIを噛ませて自動テスト＆デプロイできるような環境にするべく引き続き調査＆実験を続けたいと思います。
- また、ミドルウェアを入れたりとより本格的な環境構築にもチャレンジしていきDockerを上手く利用できるようになりたいと思います。

# 参考URL
- [Docker(公式)](https://www.docker.com/)
- [ドットインストール Docker入門 (全11回)](https://dotinstall.com/lessons/basic_docker)
- [Quickstart: Compose and Rails](https://docs.docker.com/compose/rails/)
- [docker-composeでRailsを開発しCircleCI 2.0でテストしてHerokuにdeployする](https://qiita.com/Kesin11/items/47079bc7f659e71b694c)
- [Container Registry & Runtime (Docker Deploys)](https://devcenter.heroku.com/articles/container-registry-and-runtime)