Rails & Nuxt.jsのDocker環境をalpineイメージで構築

Ruby on Rails、Nuxt.js、MySQLのDocker環境を作成します。
Rails、Nuxtのalpine環境の構築手順はそれぞれだと多くあるのですが、まとまったものがあまり ~~ない~~ 見つからなかったので、復習を兼ねてポストを作成します。

## 準備

### ディレクトリ作成

作業ディレクトリは任意です。

```terminal
$ NEW_APP=rails-nuxt-app #任意のアプリ名
$ mkdir ${NEW_APP}
$ cd ${NEW_APP}
$ mkdir ./backend ./frontend
```

`backend` はRails用、`frontend` はNuxt用のディレクトリです。
まずは下記のファイルを修正していきます。

```text
.
├ backend
│   ├ Dockerfile
│   ├ Gemfile
│   └ Gemfile.lock
│
├ frontend
│   └ Dockerfile
│
├ docker-compose.yml
└ .env
```

### docker-compose.yml

#### `.env`

docker-compose.ymlで参照する環境変数を記載します。
ここではMySQLのrootパスワード、RailsおよびNuxt環境のホスト、ポート番号のみ定義します。
RailsとNuxtは、共にデフォルトのポートが `3000` 番なので、後の利便性のためにいずれかを変えておきます。
(本記事ではNuxt側を `8080` に変更)

```properties
MYSQL_ROOT_PASSWORD=password

BACKEND_HOST=0.0.0.0
BACKEND_PORT=3000

FRONTEND_HOST=0.0.0.0
FRONTEND_PORT=8080
```

#### `./docker-compose.yml`

.envで定義した変数を参照しています。
docker-compose.ymlの `environment`、 Dockerfileの `ENV` で同じ環境変数が定義されていた場合は、前者が使用されます。
(本記事ではDockerfile単体でもイメージ作成できるように環境変数の記載を残していますが、docker-compose.ymlに定義されていれば問題ありません)

下記はDockerボリュームを作成します。

* mysqlのdatadir
* rubyのgem_home
* nodeのnode_modules

```yml:docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7.27
    restart: always
    volumes:
      - db-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
  backend:
    build: ./backend
    ports:
      - ${BACKEND_PORT}:3000
    command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s -b ${BACKEND_HOST}"
    volumes:
      - ./backend:/app
      - backend-bundle:/usr/local/bundle
    environment:
      - HOST=${BACKEND_HOST}
      - PORT=${BACKEND_PORT}
    depends_on:
      - db
    tty: true
    stdin_open: true
  frontend:
    build: ./frontend
    ports:
      - ${FRONTEND_PORT}:8080
    command: /bin/sh -c "yarn dev"
    volumes:
      - ./frontend:/app
      - frontend-node_modules:/app/node_modules
    environment:
      - HOST=${FRONTEND_HOST}
      - PORT=${FRONTEND_PORT}
    tty: true
volumes:
  db-data:
  backend-bundle:
  frontend-node_modules:
```

### backend

Rails環境構築のためのファイルを準備します。

#### `./backend/Gemfile`

Railsのバージョンのみ指定しておきます。

```ruby:Gemfile
source 'https://rubygems.org'

gem 'rails', '5.2.3'
```

#### `./backend/Gemfile.lock`

空ファイルをtouchしておけばOKです。

#### `./backend/Dockerfile`

Alpine Linuxのパッケージは最低限のものだけインストールします。
開発を進めるうちにgemのインストールで依存エラーが発生した場合には、不足パッケージを都度追加しましょう。
面倒な場合は `build-base` を `RUNTIME_PACKAGES` に移しておけば一旦は大丈夫です。

```Dockerfile:Dockerfile
FROM ruby:2.6.3-alpine

ENV RUNTIME_PACKAGES "mysql-client mysql-dev tzdata nodejs"
ENV DEV_PACKAGES "build-base curl-dev"
ENV APP_HOME /app
ENV TZ Asia/Tokyo

ENV HOST 0.0.0.0
ENV PORT 3000

WORKDIR ${APP_HOME}
ADD Gemfile ${APP_HOME}/Gemfile
ADD Gemfile.lock ${APP_HOME}/Gemfile.lock

RUN apk update \
    && apk upgrade \
    && apk add --update --no-cache ${RUNTIME_PACKAGES} \
    && apk add --update --no-cache --virtual=.build-dependencies ${DEV_PACKAGES} \
    && bundle install -j4 \
    && rm -rf /usr/local/bundle/cache/*.gem \
        && find /usr/local/bundle/gems/ -name "*.c" -delete \
        && find /usr/local/bundle/gems/ -name "*.o" -delete \
    && apk del --purge .build-dependencies \
    && rm -rf /var/cache/apk/*

COPY . ${APP_HOME}

EXPOSE ${PORT}

CMD ["rails", "server", "-b", ${HOST}]
```

### frontend

#### `./frontend/Dockerfile`

```Dockerfile:Dockerfile
FROM node:12.9.0-alpine

ENV APP_HOME /app
ENV PATH ${APP_HOME}/node_modules/.bin:$PATH
ENV TZ Asia/Tokyo

ENV HOST 0.0.0.0
ENV PORT 8080

WORKDIR ${APP_HOME}
ADD . ${APP_HOME}

RUN apk update \
    && apk upgrade \
    && yarn install \
    && rm -rf /var/cache/apk/*

EXPOSE ${PORT}

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

## アプリケーション作成

Rails、Nuxt環境にプロジェクトを作成します。
`docker-compose run` を実行したタイミングで、それぞれのDockerイメージがbuildされ、さらにコンテナが立ち上がります。
`--no-deps` ... docker-compose.ymlで `depends_on` or `links` 指定するサービスは起動しない。
`--rm` ... 処理を終えたコンテナを自動的に削除。

### backend

#### アプリケーション作成

`rails new` でRailsアプリケーションを作成します。
`--api` オプションでAPIモードにしていますが、不要な方は外してください。

```bash
$ docker-compose run --no-deps --rm backend rails new . --force --api --database=mysql --skip-bundle
```

DB接続のため、下記のファイルを修正します。

```text
.
└ backend
    ├ config
    │  └ database.yml
    ├ Gemfile
    └ .env
```

#### `./backend/.env`

docker-compose.ymlで参照している `MYSQL_ROOT_PASSWORD` と同じもの設定します。

```properties
MYSQL_ROOT_PASSWORD=password
```

#### `./backend/Gemfile`

.envから環境変数を読み込む[dotenv-rails](https://github.com/bkeepers/dotenv)というgemを追加します。

```ruby:Gemfile
~~省略~~

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]

  gem "dotenv-rails" #追加
end

~~省略~~
```

#### `./backend/config/database.yml`

DBへのアクセスに使用するパスワードを、環境変数から取得します。

```ruby:database.yml
~~省略~~

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: <%= ENV.fetch("MYSQL_ROOT_PASSWORD") { '' } %> #環境変数を参照するように修正
  host: db #localhostからdocker-compose.ymlのサービス名に修正

~~省略~~
```

### frontend

#### アプリケーション作成

`npx` でNuxtアプリケーションを作成します。  
後から追加/変更できるので、このタイミングではEnterキー連打でOKです。


```terminal
$ docker-compose run --rm frontend npx create-nuxt-app .

~~省略~~

create-nuxt-app v2.10.0
✨  Generating Nuxt.js project in .
? Project name #アプリ名 <Enter>
? Project description #任意 <Enter>
? Author name #任意 <Enter>
? Choose the package manager #Yarn <Enter>
? Choose UI framework #None <Enter>
? Choose custom server framework #None <Enter>
? Choose Nuxt.js modules #(Nothing) <Enter>
? Choose linting tools #(Nothing) <Enter>
? Choose test framework #None <Enter>
? Choose rendering mode #Universal (SSR) <Enter>
? Choose development tools #(Nothing) <Enter>
```

## Dockerイメージ作成

アプリケーション作成時にできたDockerボリュームは削除しておきます。

```terminal
$ docker-compose down --volume
# もしくは docker volume rm ボリューム名
```

各ファイルを修正した状態で、Dockerイメージをビルドします。

```terminal
$ docker-compose build
```

docker-compose.ymlで `build` を指定しているbackendとfrontendのイメージが作成されたことを確認します。

```terminal
$ docker images --format "{{.Repository}}\t{{.CreatedSince}}" ${NEW_APP}*
rails-nuxt-app_frontend     About a minutes ago
rails-nuxt-app_backend      About a minutes ago
```

## Hello World

最後にDockerコンテナを起動し、Rails、NuxtアプリケーションのHelloWorldを確認します、

docker-compose.ymlで定義したサービスを `-d` オプション(デタッチモード)でバックグラウンド起動します。

```terminal
$ docker-compose up -d
```

プロセスが立ち上がっていることを確認します。

```terminal
$ docker-compose ps
        Name                       Command               State           Ports         
---------------------------------------------------------------------------------------
rails-nuxt-app_backend_1    /bin/sh -c rm -f /app/tmp/ ...   Up      0.0.0.0:3000->3000/tcp
rails-nuxt-app_db_1         docker-entrypoint.sh mysqld      Up      3306/tcp, 33060/tcp   
rails-nuxt-app_frontend_1   docker-entrypoint.sh /bin/ ...   Up      0.0.0.0:8080->8080/tcp
```
失敗している場合は `docker-compose logs` などで原因を探りましょう。

### backend

RailsアプリケーションのDBを作成します。

```terminal
$ docker-compose exec backend rails db:create
Created database 'app_development'
Created database 'app_test'
```

ブラウザで http://localhost:3000/ を開きます。
<img width="855" alt="rails-helloworld.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123634/770cd186-4893-ecf1-4845-ccef7e1e84ac.png">

### frontend

ブラウザで http://localhost:8080/ を開きます。
<img width="855" alt="nuxt-helloworld.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123634/45e3c37f-7bf0-d8e1-6495-8b0dab6ebb44.png">

---

お疲れさまでした。
次回は [GraphQL](https://graphql.org/) の導入について投稿したいと思います。