概要
新規に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 | 
ディレクトリ構成
.
├── 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)
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
#!/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(フロントエンド)
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の設定
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の設定
# ソケット通信を行うためコメントアウト
# port        ENV.fetch("PORT") { 3000 }
# こちらを新しく記述
bind "unix:///myapp/tmp/sockets/puma.sock"
手順3: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コンテナの中の
    volumes_from:
      - api
です。
これを記述することによって、API側DockerfileのVOLUMEで指定した/tmpディレクトリがfrontendコンテナにマウントされ、apiコンテナとソケット通信ができるようになります。
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でコンテナの名前をdb 、 MYSQL_ROOT_PASSWORDを password としているため、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つのコンテナの STATUS が running になっていれば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環境を構築する記事はそれなりに見つけられたものの、この記事と同じような構成で作られたアプリの記事をなかなか見つけることができず苦労しました。なので、自分と同じような構成でアプリケーションを作成しようとしている方の参考になれば幸いです。
拙い文章でしたが最後まで読んでいただき、本当にありがとうございます。