docker-composeを使った開発環境構築実践！ Rails + Nuxt.js + MySQLを動かす

# はじめに
docker-composeを使ったローカル開発環境構築について解説します！

ここでは、フロントエンドにNuxt.js, バックエンドにRails, DBにMySQLを使った構成にします。
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751408/3ee6b718-36ea-fd8b-af37-9a8e37cda90c.png' width='350'>

# YouTube動画
動画で確認したい方はこちらもどうぞ！
[【YouTube動画】 docker-compose による開発環境構築入門](https://youtu.be/CgWQmT0hQaM)
[![docker-compose による開発環境構築入門](https://img.youtube.com/vi/CgWQmT0hQaM/0.jpg)](https://youtu.be/CgWQmT0hQaM)

# GitHub
GitHubにコードを載せているので、実際に動かしてみたい方はこちらもどうぞ！
https://github.com/yassun-youtube/docker-compose-sample

```bash
git clone git@github.com:yassun-youtube/docker-compose-sample.git
```

<details>
<summary>実際のコード</summary>
<div>

```docker-compose.yml
version: "3.8"
services:
  rails:
    build: ./rails-sample
    ports:
      - 3000:3000
    volumes:
      - ./rails-sample:/app
      - /app/node_modules
    stdin_open: true
    tty: true
    command: sh -c "yarn && bundle exec rails server -b 0.0.0.0 -p 3000"
    depends_on:
      - mysql
  nuxt:
    build: ./nuxt-sample
    ports:
      - 8000:8000
    volumes:
      - ./nuxt-sample:/app
      - /app/node_modules
    command: sh -c "yarn && yarn dev"
  mysql:
    image: mysql:8.0.22
    volumes:
      - db-data:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
      MYSQL_ROOT_HOST: '%'
      MYSQL_DATABASE: test-database
volumes:
  db-data:
```

```Dockerfile
# ./nuxt-sample/Dockerfile
FROM node:14.15.1-alpine3.12

WORKDIR /app

COPY package.json yarn.lock ./

RUN yarn install

CMD ["yarn", "dev"]
```

```Dockerfile
# ./rails-sample/Dockerfile
FROM ruby:2.7.1-alpine

RUN set -x && apk add --no-cache \
                      # for mysql
                      mysql-dev \
                      # for nokogiri
                      build-base \
                      # for tzinfo-data
                      tzdata \
                      yarn
WORKDIR /app

COPY Gemfile Gemfile.lock package.json yarn.lock ./

RUN bundle install
RUN yarn install

CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"]
```

```database.yml
default: &default
  adapter: mysql2
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  username: root
  password:
  database: rails-sample-dev
  host: mysql

test:
  <<: *default
  username: root
  password:
  database: rails-sample-test
  host: mysql
```

```nuxt.config.js
...
  proxy: {
    '/api': 'http://rails:3000'
  },
...
```

</div>
</details>

# docker-composeとは
docker-composeとは複数コンテナやネットワーク、ボリュームを一括管理できるツールです。
Dockerをインストールすれば、ついてくるので、別途インストールする必要はありません。

# よく使うコマンド
docker-composeでよく使うコマンドを紹介します。

## docker-compose up
docker-compose.ymlに記載されたコンテナを起動するために使います。
``-d`` オプションを付けると、デタッチモードで起動します。

## docker-compose down
起動したコンテナを停止して削除するときに使います。
削除しない場合は、CTRL + Cを押すか ``docker-compose stop``で止めることができます。

コンテナをデタッチモードで動かしているときは、``docker-compose ps``と併用して使います。

## docker-compose build
docker-compose.ymlに記載されたサービス (nuxtとかrails, mysql) をビルドするときに使います。
ymlファイル内の``image: ``またはbuildに書かれているパスにあるDockerfileのイメージを参照して、ビルドを開始します。

## docker-compose run <サービス名> <コマンド>
docker-compose.ymlに記載されたサービスを指定してコマンドを実行するときに使います。

## docker-compose exec <サービス名> <コマンド>
既に起動しているコンテナを指定して、コマンドを実行するときに使います。

# docker-compose.ymlの概要
docker-compose.ymlで指定できる、``version`` ``services`` ``networks`` ``volumes``について紹介します。

## version
docker-composeの書き方のバージョンを指定するときに使います。
この記事を書いている段階 (2020/11/29) では、3.8です。

## services
コンテナを定義するために使います。
ここで、railsとかnuxt, mysqlのDockerfileを指定したり、イメージを直接指定して使います。

## networks
各サービスがどのネットワークと接続するかを定義できます。


## volumes
ホストOSとゲストOSでデータを同期するときに使います。
今回はMySQLのデータを同期するために使います。

# 実際の設定ファイル
実際の設定ファイルは以下のようになります。

```docker-compose.yml
version: "3.8"
services:
  rails:
    build: ./rails-sample
    ports:
      - 3000:3000
    volumes:
      - ./rails-sample:/app
      - /app/node_modules
    stdin_open: true
    tty: true
    command: sh -c "yarn && bundle exec rails server -b 0.0.0.0 -p 3000"
    depends_on:
      - mysql
  nuxt:
    build: ./nuxt-sample
    ports:
      - 8000:8000
    volumes:
      - ./nuxt-sample:/app
      - /app/node_modules
    command: sh -c "yarn && yarn dev"
  mysql:
    image: mysql:8.0.22
    volumes:
      - db-data:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
      MYSQL_ROOT_HOST: '%'
      MYSQL_DATABASE: test-database
volumes:
  db-data:
```

## 大枠の設定
上記設定ファイルの大枠をみていきます。
設定ファイルはバージョン3.8を利用し、サービスにはrails, nuxt, mysqlを指定しています。
DBのデータを同期するために、``volumes: db-data:``を指定しています。

```docker-compose.yml
version: "3.8"
services:
  rails: ...
  nuxt: ...
  mysql: ...
volumes:
  db-data:
```


## services: mysqlの設定
imageでMySQL 8.0.22のDockerイメージを取得し、volumesでデータを同期しています。
environmentで環境変数を設定でき、ここではrailsからROOTユーザーとして、アクセスできるようにしています。
また、作成するDB名はtest-databaseとしました。

```docker-compose.yml
mysql:
  image: mysql:8.0.22
  volumes:
    - db-data:/var/lib/mysql
  environment:
    MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
    MYSQL_ROOT_HOST: '%'
    MYSQL_DATABASE: test-database
```

## services: nuxtの設定
nuxtは別途Dockerfileを作成し、そこからイメージを取るようにしました。
Dockerfileの場所をbuildで指定しています。

portsでは、ホストOSの8000ポートとゲストOSの8000ポートを繋げる設定をしています。

volumesの1行目``./nuxt-sample:/app``は、ホストOSのnuxt-sampleをゲストOSのappと同期するための設定です。
2行目の``/app/node_modules``はホストOSのnode_moduleとゲストOSのnode_moduleを同期しないための設定です。
node_modulesはOS依存のパッケージが入る可能性があるので、このように除外しています。

commandでは、コンテナ起動時にシェルで実行するコマンドを設定しています。
node_modulesを作るために、``yarn``を実行し、``yarn dev``でnuxtを起動しています。

```docker-compose.yml
nuxt:
  build: ./nuxt-sample
  ports:
    - 8000:8000
  volumes:
    - ./nuxt-sample:/app
    - /app/node_modules
  command: sh -c "yarn && yarn dev"
```

次にDockerfileをみていきます。
FROMで使用するDockerイメージを指定します。
開発環境なので、軽量なalpineにしています。

WORKDIRで以後、コマンドを実行するディレクトリを指定します。

COPY, RUN, CMDでパッケージをゲストOSにコピーし、yarn, yarn devを実行しています。
docker-compose.yml側で同じ設定をしているので、なくても大丈夫です。

```Dockerfile
# ./nuxt-sample/Dockerfile
FROM node:14.15.1-alpine3.12

WORKDIR /app

COPY package.json yarn.lock ./

RUN yarn install

CMD ["yarn", "dev"]
```

## services: railsの設定
build, ports, volumesは先ほどと同じような意味です。

dockerでbinding.pryなどのデバッグができるように、``stdin_open: true``と``tty: true``を指定しています。

depends_onでは、mysqlを指定し、mysqlのコンテナが起動するまではrailsのコンテナが起動しないようにしています。

```docker-compose.yml
rails:
  build: ./rails-sample
  ports:
    - 3000:3000
  volumes:
    - ./rails-sample:/app
    - /app/node_modules
  stdin_open: true
  tty: true
  command: sh -c "yarn && bundle exec rails server -b 0.0.0.0 -p 3000"
  depends_on:
    - mysql
```

Dockerfileの方もnuxtと同じように依存パッケージをインストールして、railsサーバーを起動しています。

```Dockerfile
# ./rails-sample/Dockerfile
FROM ruby:2.7.1-alpine

RUN set -x && apk add --no-cache \
                      # for mysql
                      mysql-dev \
                      # for nokogiri
                      build-base \
                      # for tzinfo-data
                      tzdata \
                      yarn
WORKDIR /app

COPY Gemfile Gemfile.lock package.json yarn.lock ./

RUN bundle install
RUN yarn install

CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"]
```

## database.ymlの設定
RailsサーバーとMySQLを接続するために、docker-compose.ymlのサービスで指定している名前をdatabase.ymlで設定する必要があります。

``services: mysql``と設定していたので、database.ymlのhostにもmysqlと設定しておきます。

```database.yml
default: &default
  adapter: mysql2
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  username: root
  password:
  database: rails-sample-dev
  host: mysql

test:
  <<: *default
  username: root
  password:
  database: rails-sample-test
  host: mysql
```

## nuxt.config.jsの設定
NuxtからRailsにプロキシしたい場合は、nuxt.config.jsのproxyにサービス名を記述するだけでプロキシできます。

```nuxt.config.js
...
  proxy: {
    '/api': 'http://rails:3000'
  },
...
```

# おわりに
実際にコードを見ながら、動かしながら学んだ方が理解が深まると思うので、ぜひGitHubのリポジトリもご確認ください！
https://github.com/yassun-youtube/docker-compose-sample