M1macでRails6とNuxt3の環境をDockerで作る

# 前提
Apple M1チップのMacBook Air(macOS Big Sur, 8GB)

## 構成
#### Server
Rails6.14 API mode
Mysql5.7

#### Front
Vue3/Nuxt3/TypeScript


# この記事でやること
M1 macを使用して、Rails6.14 + Nuxt.js 3 の環境をDockerで作っていきます。
レポジトリもそれぞれAPIサーバーとフロントサーバーに分ける想定なので、ニコイチで作りたい方は
`docker-compose.yml`を一つにしてよしなに対応お願いします。
また、できるだけ参考にした記事などは共有しますが漏れていた場合は追記しますのでご指摘ください。

# 動機
仕事ではdockerを使ってはいるものの自分で一からセットアップしたことがなかったせいで
結構つまづく部分が多かったので作ってみたかった + 備忘録的に書いておこうと思った次第です。

今回この構成でサービスを作る予定なので宣伝も兼ねて環境構築手順を共有していきます。
サービスができたらこの下あたりにでもリンクを貼るのでお暇な方はどうぞ。
いつになったらリリースするのかとかは聞かないでください笑
ここ⇨

# Railsの環境構築
全体の構成などはこちらの記事を参考にしました。

https://zenn.dev/kkosuke/articles/c3f129195a07ad

自分の場合と異なり、こちらの記事ではdocker imageにaplineを、dbにpostgresを採用しているため多少の違いがあります。
alpineやpostgresを使用したい場合はこちらを参考にするといいかもです。
ちなみにalpineを採用するメリットは軽量化(リリース時間が短くなるなど)だと思うのですが以前pdf作成機能を作った時に
imagemagickなどが入っておらず苦労した覚えがあるため使用していません。
alpineの良し悪しに関する考察はこちらを参考にどうぞ

https://blog.inductor.me/entry/alpine-not-recommended

## 作業用ディレクトリを用意
ディレクトリ名などは各々好きに決めてください。

```
$ /Users/ユーザー名/workspace/rails-api
```

## ディレクトリ構成
```
rails-api
  └.env
  └Dockerfile
  └Gemfile
  └Gemfile.lock
```

それでは一つずつ解説して行きます。

## .envファイル
```
# commons
WORKDIR=rails-api <-ここをrootディレクトリ名と同じにする
CONTAINER_PORT=3000
FRONT_PORT=8080
API_PORT=3000

# db
MYSQL_ROOT_PASSWORD=password
```
https://matsuand.github.io/docs.docker.jp.onthefly/compose/environment-variables/

> 環境変数が複数ある場合、.envという名前のファイルに環境変数のデフォルト値を設定することができます。 あるいはコマンドラインオプション--env-fileを使って環境変数ファイルへのパスを指定して利用することもできます。

公式にあるように、デフォルトで採用されている環境変数用のファイルです。
今回はこちらに環境変数を記載して行きます。

## docker-compose.yml
```yml:docker-compose.yml
version: '3'
services:
  db:
    image: mysql/mysql-server:5.7.28
    container_name: db
    environment:
      MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD
      MYSQL_ROOT_HOST: '%' # コンテナ外からdbに接続するために記載
    ports:
      - "3306:3306"
  # command: --default-authentication-plugin=mysql_native_password <- mysql8.0の場合に必要

  api:
    stdin_open: true
    tty: true
    build:
      context: .
      args:
        WORKDIR: $WORKDIR
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD
    depends_on:
      - db
    ports:
      - "$API_PORT:$CONTAINER_PORT"
    volumes:
      - .:/rails-api
```

注意すべきは以下2点です。
1 . `MYSQL_ROOT_HOST: '%'` 
  これはapiコンテナからdbコンテナに接続する場合に必要な設定になります。これを書かなかった場合、コンテナにログインしてmysqlを操作することはできますが、コンテナ外部から操作することができなくなります。
参考記事：

https://qiita.com/a1k4r/items/0c39ada24639795d6a75

2 . `command: --default-authentication-plugin=mysql_native_password`
こちらはmysql8.0を使用したい場合に必要になる設定です。他記事をみていますとmy.confに書いている方もいますが、今回はより簡易な手法としてこちらを記載しておきます。
参考記事：

https://www.trifields.jp/how-to-use-mysql-8-0-on-docker-4514
 


## Dockerfile
```Dockerfile:Dockerfile
FROM ruby:3.0.2

ARG WORKDIR

ENV HOME=/${WORKDIR} \
    LANG=C.UTF-8 \
    TZ=Asia/Tokyo

WORKDIR ${HOME}
COPY Gemfile* ./

RUN wget --quiet -O - /tmp/pubkey.gpg https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list
RUN set -x && apt-get update -y -qq && apt-get install -yq nodejs yarn
RUN bundle install

COPY . .
```

https://blog.cloud-acct.com/posts/u-env-docker-compose/

環境変数の設定に関しては上記の記事を参考にさせていただきました。
`ARG`や`ENV`、`${WORKDIR}`などは`.env`ファイルから取得した環境変数をDockerfile、docker-compose.yml、コンテナで使用できるようにするためのものです。
詳細は上記の記事を読んでいただけると理解できると思いますので割愛します。

この記事ではパッケージマネージャーにapt-getを使用していますが、alpine環境を使用する場合はapkを使用する必要があるそうです。

## Gemfile

```:Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

gem 'rails', '~> 6.1.4', '>= 6.1.4.4'
```

## Gemfile.lock
こちらはからファイルを作成します。

## 手順
1 . まずは上で解説したディレクトリ構成でファイルを作成しましょう。
2 . Dockerfileを実行し、docker imageを作成します。

```
$ docker-compose build
```
3 . imageができたか確認します。

```
REPOSITORY               TAG       IMAGE ID       CREATED        SIZE
rails-api_api            latest    0d08728fc697   26 hours ago   1.01GB
```

4 . コンテナを起動してrailsアプリを作成します。以下コマンドは2で作成したdocker imageからapiコンテナを起動してrails newするものです。ここでコンテナ内にrailsアプリができた場合は自分のホスト環境のrails-apiディレクトリ配下にもrailsアプリが作成されているはずです。

```
$ docker-compose run --rm api rails new . -f -B -d mysql --api
```

5 . アプリが作成できたら、自分のホスト環境のdatabase.ymlファイルを修正しコンテナ内のdbと接続できるようにします。

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

6 . database.ymlを修正したらもう一度ビルドし直し、dbを作成します。

```
$ docker-compose build
$ docker-compose run --rm api rails db:create
```

7 . dbが作成できたらdocker環境を立ち上げます。

```
$ docker-compose up
```

うまく立ち上がったら以上でrailsアプリの環境構築は完了です。


# Nuxt3の環境構築
Nuxt3の環境もRailsの環境と大枠では一緒です。

ディレクトリ構成

```
nuxt-front
 L .env
 L docker-compose.yml
 L Dockerfile
```

## .env

```:.env
# commons
WORKDIR=nuxt-front
CONTAINER_PORT=3000
FRONT_PORT=8080
API_DOMAIN=172.20.0.3:3000
API_ACCESS_URL=http://172.20.0.3:3000/graphql

```

API_DOMAINに使用しているIPは以下の方法で取得してください

```
$ docker ps
$ docker exec -it {apiコンテナのid} bash
$ hostname -i
```

## docker-compose.yml

```yml:docker-compose.yml
version: '3'
services:
  nuxt:
    environment:
      API_DOMAIN: $API_DOMAIN
      API_ACCESS_URL: $API_ACCESS_URL
    build:
      context: .
      args:
        WORKDIR: $WORKDIR
    tty: true
    command: yarn dev
    volumes:
      - .:/nuxt-front
    ports:
      - "$FRONT_PORT:$CONTAINER_PORT"
```

## Dockerfile

```Dockerfile:Dockerfile
FROM node:17-slim

ARG WORKDIR
ARG CONTAINER_PORT
ARG API_URL
ARG API_URL_BROWSER
ARG FRONT_PORT

ENV HOME=/${WORKDIR} \
    LANG=C.UTF-8 \
    TZ=Asia/Tokyo \
    HOST=0.0.0.0 \
    API_URL=${API_URL} \
    API_URL_BROWSER=${API_URL_BROWSER}

WORKDIR ${HOME}

RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y git && \
    yarn global add @vue/cli && \
    yarn global add @vue/cli-init

# COPY package*.json ./
# RUN yarn install
#
# COPY . ./

EXPOSE ${CONTAINER_PORT}
```

## 手順
#### 1 . 上記ディレクトリ構成通りにフォルダ・ファイルを作成する。

#### 2 . nuxt-frontディレクトリに移動する。

#### 3 . Dockerfileを実行してimageを作成する
この時、Dockerfileの下記部分をコメントアウトしておく。

```
# COPY package*.json ./
# RUN yarn install
#
# COPY . ./
```

##### 解説
・`COPY package*.json ./`
自分のホスト環境のpackage.jsonとpackage.json.lockをコンテナのWORKDIR(/nuxt-front)に配置する。
・`RUN yarn install`
配置したpackage.jsonを元にパッケージをコンテナにインストール
・`COPY . ./`
自分のホスト環境のnuxt-front配下をコンテナのnuxt-front配下にコピーする。

要は新しい機能を実装したりパッケージを追加したりした時に、自分のホスト環境の修正内容をコンテナにコピーして追加したパッケージをinstallすると言ったことを行なっている。

初めて環境構築する場合はそもそも自分のホスト環境にpackage.jsonもpackage.json.lockもないので、コメントアウトしておかないとエラーになるので注意。


###### image作成

```
$ docker-compose build
```

#### 4 . docker環境を立ち上げ、コンテナ内でnuxtアプリを作成する
まずdocker-compose.ymlの`command: yarn dev`部分をコメントアウトする。
※ コンテナにnuxtアプリがない場合エラーになってしまうため

```
$ docker-compose up
```

立ち上がったコンテナを確認する

```
$ docker ps

CONTAINER ID   IMAGE              COMMAND                  CREATED        STATUS         PORTS                    NAMES
879a1f3fa8e6   nuxt-front_nuxt   "docker-entrypoint.s…"   19 hours ago   Up 4 seconds   0.0.0.0:8080->3000/tcp   nuxt-front_nuxt_1
```

コンテナに入る

```
$ docker exec -it 879a1f3fa8e6 bash  <- 879a1f3fa8e6は上で表示されたCONTAINER IDを指定する
```

カレントディレクトリを確認

```
$ pwd

/nuxt-front
```

nuxt3アプリを作成する

```
$ npx nuxi init app

Nuxt CLI v3.0.0-27369360.33ebb01                                                                                              18:14:05
ℹ cloned nuxt/starter#v3 to /Users/ユーザー名/workspace/nuxt-front/app                   18:14:06
                                                                                                                              18:14:06
 🎉  Another pioneering Nuxt project just made! Next steps:

     📁  `cd app`

     💿  Install dependencies with `npm install` or `yarn install`

     🚀  Start development server with `npm run dev` or `yarn dev`
```

結果的に現在のコンテナでのディレクトリ構成はこうなる

```
nuxt-front
  L .degit
  L .npm
    L package.jsonなど
  L app
    L node_modules
    L app.vue
    L nuxt.config.ts
    L package.json...など
  L Dockerfile
  L docker-compose.yml
```

今回はapp配下の構成をnuxt-front配下に持ってきたいので、mvコマンドを使用してapp配下を移動させる。

```:例
$ mv app/{*,.*} ../nuxt-front
```

その後からになったappディレクトリを削除する。

```
$ rm -rf app
```

また、`npx nuxi init app`を実行すると.degitファイルと.npmディレクトリが作成されるが、
nuxtアプリとして本来必要なのはapp配下で、.degitと.npmは一部を除いて使用しないので最終的にはgitに含めなくても良いはず。

##### ※.degitと.npmについて
この二つのファイル/ディレクトリはnuxt3をインストールする際に必要なnuxiパッケージを事前にインストールするために作成されるファイルと思われる。.nmp配下のpackage.jsonに以下の記述があるので、`npx nuxi init app`コマンドを叩いたときに.degitと.npmを作成 + npm installまでを自動で行なっているはず。(詳しく調べてないので間違ってたらコメントください。)

```json:.npm/package.json
"devDependencies": {
    "nuxi": "^0.10.1"
  }
```

なぜnuxiをインストールしているかというと、先ほどまでapp配下にあったpackage.jsonにスクリプトが作成されており、そこでnuxiコマンドを使用しているため。 サーバー立てる時と本番用にコンパイルするときに使用するっぽい。
Dockerfileでわざわざ2回に分けてyarn installしたくないのでこの内容をpackage.jsonに追加しておく。
追加できたら.degitと.npmは削除する。

```json:package.json
{
  "private": true,
  "scripts": {　
    "dev": "nuxi dev",      <- ここでnuxiが使用されている
    "build": "nuxi build",  <- ここでnuxiが使用されている
    "start": "node .output/server/index.mjs"
  },
  "devDependencies": {
    "nuxt3": "latest",
    "nuxi": "^0.10.1" <- ここに追加
  }
}
```

#### 5 . docker環境を立ち上げる
Dockerfileとdocker-compose.ymlのコメントアウトを外す


```Dockerfile:Dockerfile
COPY package*.json ./
RUN yarn install

COPY . ./
```

```yml:docker-compose.yml
version: '3'
services:
  nuxt:
    build:
      context: .
      args:
        WORKDIR: $WORKDIR
    tty: true
    # command: yarn dev   <- ここのコメントアウトを外す
    volumes:
      - .:/nuxt-front
    ports:
      - "$FRONT_PORT:$CONTAINER_PORT"
```
`command: yarn dev`はdocker-compose up`した際にサーバーを立ち上げる。
実際には`yarn dev`するとpackage.jsonに定義されていたscriptの`nuxi dev`が実行されるため、
この後`docker-compose up`する際に`nuxi: not found`などのエラーが出ていた場合はpackage.jsonに
nuxiの記述が足りないため追加する。

```json:package.json
"devDependencies": {
    "nuxt3": "latest",
    "nuxi": "^0.10.1" <- ここが足りない
  }
```

docker環境立ち上げ

```
$ docker-compose up

Nuxt CLI v3.0.0-27369360.33ebb01
nuxt_1  | 
nuxt_1  |   > Local:    http://localhost:3000/ 
nuxt_1  |   > Network:  http://172.20.0.2:3000/
nuxt_1  | 
```

無事環境が立ち上がったらブラウザで http://localhost:8080/　 にアクセスして以下の画面が出れば動作確認完了です。
![スクリーンショット 2022-01-16 19.04.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152846/4acaad62-b3f5-bc4b-15b0-82e6faf126cb.png)
スクリーンショット 2022-01-16 19.04.17

また、初めて`yarn dev`した時に.nuxtディレクトリが作成されますが、`docker-compose up`したら勝手に作成されるので
gitに含めなくても大丈夫です。


## 後書き
まとまりなく書いてしまったのですが誰かの役に立てれば幸いです。
今回作成した環境でサービス開発していく予定なので、何か記事化できそうなことがあれば書いて行くつもりです。
予定では以下のものを使用するつもりなのでそのうち記事にするかもです。

Graphql
ユーザー認証: railsのdeviseかfirebase authか
Github project(使用中)
Github ActionsでCI/CD
決済： Pay.jpかstripe
Atomic design
Vuetify.js
などなど

## 追記(2022年1月21日)
nuxtのdocker-compose.ymlなのですが以下の記述を追加しないと無限リロードすることがわかりました。
原因はvite関連だとは思うのですが調査中です。どなたかわかる方いたらコメントください。

```
version: '3'
services:
  nuxt:
    build:
      context: .
      args:
        WORKDIR: $WORKDIR
    tty: true
    # command: yarn dev
    volumes:
      - .:/nuxt-front
    ports:
      - "$FRONT_PORT:$CONTAINER_PORT"
      - 24678:24678 # これを入れないとnuxtが無限リロードしてしまう 
```