search
LoginSignup
3

posted at

updated at

既存のRails(APIモード) × React × Nginx × MySQLで作成したSPAをDocker化

概要

新規にDockerで開発環境を構築するのではなく、既存のローカルのSPAをDocker化するために自分が行ったことをご紹介します。
また、本番環境ではWebサーバーにNginxを使用しているため、環境ごとの差分を少なくするために、開発環境でもフロントエンド側にNginxを使用しています。

バージョン

バージョン
OS macOS Catalina
ruby 2.7.0
Rails(APIモード) 6.0.4
React 17.0.2
Node 14.4.0
MySQL 8.0.28
Docker 20.10.6
Nginx 1.20.0

ディレクトリ構成

myapp
.
├── app
├── config
├── frontend
│   ├── Dockerfile
│   ├── nginx.conf
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   └── yarn.lock
├── entrypoint.sh
├── Dockerfile
├── docker-compose.yml
├── Gemfile
├── Gemfile.lock

手順1: Docker Engineのインストール

まずはdockerコマンドを使えるようにするためにDocker Engineをインストールしていきます。Docker Engineのインストールについては公式のインストール概要が参考になるかと思います。

手順2: Dockerfile等の作成

API側のDockerfileと関連するファイルの設定

Dockerfile(API)

Dockerfile(API)
FROM ruby:2.7.0

RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && apt-get install -y yarn

RUN apt-get update -qq && apt-get install -y nodejs yarn

WORKDIR /myapp

COPY ./Gemfile /myapp/Gemfile
COPY ./Gemfile.lock /myapp/Gemfile.lock

RUN gem install bundler
RUN bundle install

VOLUME /tmp

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

CMD ["rails", "server"]

ここでのポイントは、VOLUMEで/tmpにマウントポイントを作成していることです。
これを記述することで、後に設定していくフロントエンド側のコンテナにpuma.sockがマウントされ、API側のコンテナとソケット通信することが可能になります。

entrypoint.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 "$@"

コンテナが起動されるたびに実行されるスクリプトです。特定のserver.pidファイルが事前に存在する場合にサーバーが再起動しないRails固有の問題があるそうで、それを修正するためにこのスクリプトを実行します。
(参考:Quickstart: Compose and Rails

フロントエンド側のDockerfileと関連するファイルの設定

Dockerfile(フロントエンド)

Dockerfile(フロントエンド)
FROM node:14.4.0 as build
WORKDIR /home/node/app
COPY ./ /home/node/app
RUN npm install
RUN npm run build

FROM nginx:1.20.0
RUN rm -f /etc/nginx/conf.d/*
COPY --from=build /home/node/app/build /var/www
COPY ./nginx.conf /etc/nginx/conf.d/
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

ここでのポイントはマルチステージビルドを行なっていることです。
1〜5行目でローカルのReactのソースコードをビルド。ここで生成されたものをnginxコンテナの/var/wwwにコピーすることで、Nginx上でReactを動かすことができるようになります。

Nginxの設定

nginx.conf
upstream api {
  server unix:/myapp/tmp/sockets/puma.sock;
}
server {
  listen       80;
  server_name  localhost:3000;
  charset      utf-8;

  root /var/www/;
  index index.html index.htm;

  location / {
    try_files $uri /index.html;
  }

  location /api/v1 {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://api;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 10;

}

frontendコンテナの/etc/nginx/conf.d/に配置されるNginxの設定ファイルです。
Rails側のURLが/api/v1なので、http://localhost:3000/api/v1 にリクエストが来たときにはバックエンドへリクエストを流すように設定しています。

Pumaの設定

config/puma.rb
# ソケット通信を行うためコメントアウト
# port        ENV.fetch("PORT") { 3000 }

# こちらを新しく記述
bind "unix:///myapp/tmp/sockets/puma.sock"

手順3:docker-compose.ymlの作成と関連するファイルの設定

docker-compose.yml

docker-compose.yml
version: '3'

services: 
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - db-data:/var/lib/mysql
  api:
    build: 
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/myapp
      - bundle:/usr/local/bundle
    depends_on:
      - db
  frontend:
    build:
      context: ./frontend/
      dockerfile: Dockerfile
    ports:
      - "3000:80"
    volumes_from:
      - api
    depends_on:
      - api

volumes:
  db-data:
  bundle:

ここでのポイントはfrontendコンテナの中の

docker-compose.yml
    volumes_from:
      - api

です。
これを記述することによって、API側DockerfileのVOLUMEで指定した/tmpディレクトリがfrontendコンテナにマウントされ、apiコンテナとソケット通信ができるようになります。

database.yml

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

docker-compose.ymlでコンテナの名前をdbMYSQL_ROOT_PASSWORDpassword としているため、defaultの欄に以下の内容を記述しています。

  username: root
  password: password
  host: db

手順4:イメージ、コンテナの作成と実行

ここまで準備が整ったら次はコンテナの元となるイメージを作成していきます。

$ docker compose build

docker compose build で 、 docker-compose.yml を元にイメージを作成します。

$ docker compose up

イメージが作成できたら、次に docker compose up でコンテナの作成と実行を行います。

$ docker compose ps

コンテナを立ち上げたら docker compose ps でコンテナの状態を確認します。 docker-compose.yml で設定した3つのコンテナの STATUSrunning になっていればOKです。

コンテナが running であることが確認できたら次にデータベースの作成とマイグレーションを行います。

$ docker compose exec api rails db:create
$ docker compose exec api rails db:migrate

最後に http://localhost:3000にアクセスして、アプリがローカルと同じように動作すればDocker化が完了です。

まとめ

RailsとNginxや、ReactとNginxでDocker環境を構築する記事はそれなりに見つけられたものの、この記事と同じような構成で作られたアプリの記事をなかなか見つけることができず苦労しました。なので、自分と同じような構成でアプリケーションを作成しようとしている方の参考になれば幸いです。

当方エンジニアを目指して勉強中の身ですので、間違っている点などあれば忌憚なくご指摘いただければと存じます。

拙い文章でしたが最後まで読んでいただき、本当にありがとうございます。

参考文献

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3