【入門】DockerでRailsのAPIモードを環境構築しHerokuへ本番反映する手順



## はじめに

今回はDockerを使ってRailsAPIの環境構築および簡単なアプリ開発を行い、ローカルで作成したDockerコンテナをHerokuに置いて本番反映する流れを解説していきます。

Railsの環境構築およびAPIの開発そして本番反映の流れを学びたい人はぜひ参考にしてみてください。

## この記事の対象者

- Dockerを使ってRailsAPIの開発をしたい人
- Dockerで作成したアプリをHerokuを使って本番反映したい人
- ポートフォリオ開発をこれからしたいと思っている人

## この記事でやらないこと

- Railsの細かい文法説明
- Dockerについての詳しい解説
- Herokuについて詳しい解説

上記において必要箇所は解説を都度入れますが、「そもそもdockerとは」といった細かい箇所の解説については参考記事を貼っているのでそちらを参考にしていただければと思います。

## この記事の目標

- Dockerを使ったRailsAPIの開発
- Dockerで作成したコンテナをHerokuに反映し本番公開する

## DockerでRailsの環境を構築する

まずはじめにDockerでRailsの環境構築を行い、簡単なCRUDのAPIを作成していきます。

こちらに関しては公式ドキュメントを元に進めていきます。

https://docs.docker.jp/compose/rails.html

### Dockerについて

Dockerについての解説はこちらの記事が概要を掴みやすいので参考にしてみてください。

https://aiacademy.jp/media/?p=1106

https://zenn.dev/suzuki_hoge/books/2021-04-docker-picture-60fbe950136be9c7ad85

今回dockerで作るアプリの流れは下記です。

1. アプリコードの作成
2. DockerFileを作成
3. イメージを元にコンテナを作成
4. コンテナを起動しアプリを表示

![スクリーンショット 2022-08-24 20.22.50.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/70a8d11d-1397-b362-d013-e4cc79af26d3.jpeg)

### プロジェクトディレクトリを作成しDockerFileを準備

ディレクトリ名は任意ですが自分は``rails_docker_heroku``という名前で作成しました。

該当のディレクトリで下記のコマンドを実行しDockerFileを作成します。

```
% touch Dockerfile
```
ファイル内は[公式ドキュメント](https://docs.docker.jp/compose/rails.html)を参考に下記の記述を追記します。

行毎の解説(概要)はコメントにて付与しています。
```Dockerfile:Dockerfile
# FROMでベースイメージを設定
# 今回はRubyのバージョン2.7をベースイメージとして設定
FROM ruby:2.7
# nodeやyarn等の必要なライブラリをインストール
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
# Railsアプリを配置するフォルダをコンテナ内に作成
RUN mkdir /myapp
# 作成したディレクトリをワークディレクトリとして設定
WORKDIR /myapp
# ルート配下のGemfileをコンテナ内のRailsアプリを配置場所にコピー
COPY Gemfile /myapp/Gemfile
# Gemfile.lockも同様にコンテナ内にコピーする
COPY Gemfile.lock /myapp/Gemfile.lock
# Gemfileの記述を元にbundle installを実行
RUN bundle install
COPY . /myapp

# ローカルで作成したentrypoint.shファイル(後で記述)を/usr/bin/へコピー
COPY entrypoint.sh /usr/bin/
# /usr/bin/entrypoint.shに対して実行権限を付与している(chmod +x)
# これによってシェルスクリプトファイルを実行可能にする
RUN chmod +x /usr/bin/entrypoint.sh
# exec形式でデフォルトで実行するコマンドラインの引数としてentrypoint.shを指定
ENTRYPOINT ["entrypoint.sh"]
# ネットワーク上のポートを指定する
EXPOSE 3000

# Railsサーバーを稼働させる
CMD ["rails", "server", "-b", "0.0.0.0"]
```

### Gem関連のファイルを作成する

```
$ touch Gemfile 
$ touch Gemfile.lock
```

``Gemfile``には下記の記述を初期値として追加します。
```Gemfile:Gemfile 
source 'https://rubygems.org'
gem 'rails', '~> 6.0.5', '>= 6.0.5.1'
```

``` Gemfile.lock```ファイルには何も書かなくて大丈夫です。

これら2つのファイルに関しては後で実行する``rails new``コマンド実行時に書き変わります。

### docker-compose.ymlを作成する

```
$ touch docker-compose.yml
```

``docker-compose``を使うことでWebサーバーとデータベースのコンテナを一括に管理することができます。

今回はWebサーバーとデータベースの複数コンテナの操作をするコードを書いていきます。

docker-composeについての詳しい解説はこちらの記述を参考にしてみてください。

https://kitsune.blog/docker-compose#:~:text=Docker%20Compose%E3%81%A8%E3%81%AF%E3%80%81%E8%A4%87%E6%95%B0,%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82

[公式ドキュメント](https://docs.docker.jp/compose/rails.html)を参考にファイルを記述していきます。

https://docs.docker.jp/compose/rails.html

こちらも同様にコメントにてコードの解説は付与しています。

```yaml:docker-compose.yml
# docker-composeで使用するバージョンの設定
# CIツールは2系以上から使える
version: "3"
# アプリケーションを動かすための各要素をserviceとしている
# dbとwebというサービスが今回は登録されている
services:
  # データベースの設定
  db:
    # psqlのデータベースを設定
    image: postgres
    # volumeseでマウントを設定ファイルのパスを指定
    volumes:
      - postgresql-data:/var/lib/postgresql/data
    # Dockerコンテナ内での環境変数を定義
    # Rails側のdatabase.ymlに下記の情報を入れることで連携ができる(後述)
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    # DBのDockerイメージを立ち上げる際のポート番号
    ports:
      - "5432:5432"
  # webサービス(今回でいうRailsアプリ)についての設定
  web:
    # 同じディレクトリにあるdockerFileを参照
    build: .
    # Railsのサーバーを起動している
    # 立ち上げる毎にserver.pidのファイルを削除(エラー回避用)
    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"
    # webはdbに依存していることを明示
    # 接続先をdbと記載するだけで使える
    depends_on:
      - db
volumes:
  postgresql-data:
    driver: local
```

### entrypoint.shを作成

先ほど作成した``Dockerfile``内で読み込んでいた``entrypoint.sh``を作成します。

こちらに書いた記述は初回起動時のみに実行することができます。

```
$ touch entrypoint.sh
```

```sh: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関連のファイルへの記述は完了しました。

## DockerでRailsの環境を構築する

今まで作成してきた``docker-compose.yml``と``Dockerfile``を元にRailsの環境を構築していきます。

### Railsの環境を構築する

まずは先ほど作成したディレクトリ(``rails_docker_heroku``)上で下記のコマンドを実行し、Docker上にRailsの環境を構築する。

```
$ docker-compose run web rails new . --api --force --no-deps --database=postgresql
```
- RailsのAPIモードで開発をするため``--api``のオプションをつけています
- データベースはpsqlを使うので``database=postgresql``を付与しています。

なおdocker-composeコマンドについてのより詳しい解説は公式ドキュメントを参考にしてください。

https://docs.docker.jp/compose/reference/run.html

これによってイメージが作成されたのでビルドします。

```
$ docker-compose build
```

### CRUDのAPIを作成する

下記の``scafoold``コマンドを実行することで記事(Post)のCRUDを自動生成してくれます。

```
$ docker-compose run --rm web rails generate scaffold Post title:string
```

scafooldについての詳しい解説は公式ドキュメントを参照してみてください。

https://railsdoc.com/page/rails_scaffold

ルーター、コントローラー、モデル他が自動で作成されています。

ファイルを確認します。

```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :posts
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
```

```ruby:app/models/post.rb
class Post < ApplicationRecord
end
```

下記のファイルのみ``post_params``の部分を書き換えています。

```ruby:app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: [:show, :update, :destroy]

  # GET /posts
  def index
    @posts = Post.all

    render json: @posts
  end

  # GET /posts/1
  def show
    render json: @post
  end

  # POST /posts
  def create
    @post = Post.new(post_params)

    if @post.save
      render json: @post, status: :created, location: @post
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /posts/1
  def update
    if @post.update(post_params)
      render json: @post
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  # DELETE /posts/1
  def destroy
    @post.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def post_params
      params.permit(:title)
    end
end
```

### データベースの設定

``docker-compose.yml``で設定したデータベースの内容をRailsアプリ側の``config/database.yml``に記述する。

``default:``の部分のみ下記のように書き換える。

```yaml:config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: 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'] %>
```

こちらの記述は``Dockerfile``内で記述した下記の箇所に対応している。

```dockerfile:Dockerfile
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
```

なおHerokuで使う本番環境用の設定は後で記述します。

変更を反映させるためにビルドコマンドを実行します。

```
# ビルドコマンド
$ docker-compose build
# データベース作成
$  docker-compose run web rails db:create
# マイグレーション
$ docker-compose run web rails db:migrate
```

### コンテナの作成と起動

下記のコマンドを実行し作成したイメージからコンテナを立ち上げます。

```
docker-compose up
```

立ち上がったら下記にアクセスして表示が確認できれば無事成功です。

```http://localhost:3000/```

![スクリーンショット 2022-08-25 8.29.49.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/933c11b3-f122-cd40-e748-d1bee6d858ba.jpeg)

### APIの動作確認

ここからPostmanを利用してAPIの動作確認をしていきます。

Postmanの準備については下記の記事を参考にまだやってない人は進めてください。

https://yu-report.com/entry/postman/

Postman上でプロジェクトを作り、Bodyを選択し先ほど作成したAPIに送信するデータ(title)を入れます。　

リクエストはPOSTに設定します。

![スクリーンショット 2022-08-25 8.41.54.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/791f1c0b-5623-2af1-f13b-d77e56de91d4.jpeg)

その上で右側のsendを押すとAPIのリクエストが送信されます。

下記のようなレスポンスが返ってくれば成功です。

![スクリーンショット 2022-08-25 8.41.28.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/4c069856-f52d-be34-9cd8-3f4fe3c24405.jpeg)

次にGETを選択し今登録したデータが保存されているかを確認します。

![スクリーンショット 2022-08-25 8.44.07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/cd83c0af-80de-cb70-e7b0-2a9bacb140e7.jpeg)

![スクリーンショット 2022-08-25 8.44.21.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/a2b15e58-bad2-0939-fe47-e7d30dd5bb66.jpeg)

削除(DELETE)と更新(PUT)も同様に確認できるので試してみてください。

## Herokuにデプロイし本番反映する

ここまででローカルでのDockerの環境構築および、RailsAPIの動作確認が完了したのでHerokuを使って本番反映をしていきます。

Herokuはクラウド上にあるサーバーの一種で、簡単にサーバーを立てWebアプリを公開することができます。

### Herokuの準備

まだHerokuを登録していない人は下記から登録を進めてください。

https://id.heroku.com/login

またDBを利用するためにクレジットカードの登録も済ませておいてください。なお今回やるアプリは無料で作成できます。

登録が完了したら下記のサイトの手順に従ってHerokuのCLIをインストールします。

https://devcenter.heroku.com/ja/articles/heroku-cli

これによってコマンドでHerokuを操作することができるようになります。

### Heroku上でアプリの作成およびデータベースの登録

Herokuを使う準備ができたのでいよいよ先ほど作成したDockerイメージをHerokuへデプロイしていきます。

流れとしてはHeroku上にDockerコンテナを起動させてアプリを本番環境に公開していく。

![スクリーンショット 2022-08-25 9.18.48.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/8f23e0a2-7f1f-5d22-d2a4-698a076055c8.jpeg)

まずはHerokuにログインします。コマンドを実行してユーザー名とパスワードを入力します。

```
$ heroku login
```
次にherokuのコンテナレジストリにログインします。先ほど作成したディレクトリ(``rails_docker_heroku``上)で実行します。
```
$ heroku container:login
```
heroku上でアプリを作成します。任意にアプリ名は空欄にしておくと自動でheroku側で名前をつけてくれます。

すでに重複したアプリ名があると登録できないので注意してください。

```
$ heroku create 任意のアプリ名
```

postgresqlアドオンの無料プランを追加します。
```
$ heroku addons:create heroku-postgresql:hobby-dev
```

### Heroku上でアプリの作成およびデータベースの登録

Herokuで作成したデータベースとRailsアプリで設定するデータベースの連携をしていきます。

#### 環境変数の登録

まず先ほどHerokuで作成したデータベースの情報を取得します

```
$ heroku config
```
こちらのコマンドを実行すると下記のような値が返ってきます。

```
>> CLEARDB_DATABASE_URL: postgres://[ユーザー名]:[パスワード]@[ホスト名]/[データベース名]
```
ここから取得できる
- ユーザー名
- パスワード
- ホスト名
- データベース名

をHerokuの環境変数として登録をします。

```
# データベース名の登録
$ heroku config:add APP_DATABASE='データベース名' -a アプリ名

# ユーザー名の登録
$ heroku config:add APP_DATABASE_USERNAME='ユーザー名' -a アプリ名

# パスワードの登録
$ heroku config:add APP_DATABASE_PASSWORD='パスワード' -a アプリ名

# ホスト名の登録
$ heroku config:add APP_DATABASE_HOST='ホスト名' -a アプリ名
```

上記を登録した上で再度下記のコマンドを実行します。

```
$ heroku config
```

登録した環境変数が出てきたら成功です。

#### database.ymlの書き換え

次に今登録したHerokuの環境変数を``database.yml``に反映します。

本番設定の``production``の記述を下記のように変更します。

```yaml:config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: 5
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test
  host: db

# 変更箇所
production:
  <<: *default
  database: <%= ENV['APP_DATABASE'] %>
  username: <%= ENV['APP_DATABASE_USERNAME'] %>
  password: <%= ENV['APP_DATABASE_PASSWORD'] %>
  host: <%= ENV['APP_DATABASE_HOST'] %>
```

以上でHeroku側のデータベースと連携する作業は完了しました。

この段階で一度ローカル環境のdockerは停止します。
```
$ docker-compose down
```


そしたら下記のコマンドを実行します。

```
# イメージの作成
$ docker-compose build

# heroku側にイメージの作成
$ heroku container:push web

# マイグレーション
$ heroku run rails db:migrate

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

最後に下記のコマンドを入力して実際にアプリが表示されるかを確認します。

```
$ heroku open
```

![スクリーンショット 2022-08-25 10.02.21.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/9da79ca5-0a2b-df60-361b-bfba67830114.jpeg)

ここでセキュリティーのエラーが出てきたら下記のコードを追加します。

```ruby:config/environments/development.rb
config.hosts << "herokuにデプロイしたアプリのURL"
```

再度こちらのコマンドを実行します。

```
# イメージの作成
$ heroku container:push web

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

これで無事にDockerで作成したコンテナをHerokuに反映し本番反映ができました。

APIの動作確認もPostmanで確認することができます。

## 最後に

今回はDockerでRailsAPIを開発しHerokuに本番反映する流れを備忘録として残しました。

ぜひポートフォリオを作成する方やこれからDockerを使ってアプリ開発をする方は参考にしていただければなと思います。

他にもいろいろな記事を出しているので合わせて読んでいただけると嬉しいです。

https://qiita.com/KNR109/items/5d4a1954f3e8fd8eaae7

https://qiita.com/KNR109/items/ab88c8f9a014fdccfba3

https://qiita.com/KNR109/items/7cf6b24bed318dab5715