Rails × Vue をdocker-composeで環境構築する

# はじめに
アプリ構成を

* Backend: Rails
* Frontend: Vue.js

でDockerで環境構築した時の備忘録です。



# 使用技術

* Rails: 6.0.4
 * ruby: 2.7.5
* MySQL: 8.0.28
* Vue: 3
 * node: 16.13.2
 * ブロジェクト作成ツール: Vite



# 作業内容

1. ディレクトリ/ファイルの作成
2. RailsのDocker環境を構築する
3. VueのDocker環境を構築する



# 1. ディレクトリ/ファイルの作成

ディレクトリ構造は以下の通りです。

* rootディレクトリ
 * docker-compose.yml: 複数コンテナの設定
 * .env: 環境変数の管理
 * .gitignore: Gitで追跡させないファイルの設定
 * backend: Rails構築のためのファイル群
 * frontend: Vue構築のためのファイル群

```shell:ターミナル

% tree　root_directory
.
├── docker-compose.yml
├── .env
├── .gitignore
├── backend
│   ├── Dockerfile
│   ├── Gemfile
│   ├── Gemfile.lock
│   ├── entrypoint.sh
├── frontend
   　　└── Dockerfile


```


# 2. RailsのDocker環境を構築する

以下の通り、5つのファイルを作成/編集します。

* .gitignore
* .env
* docker-compose.yml
* backend/Dockerfile
* frontend/Dockerfile


以降は、コードを書いていくので
まず、作業ブランチを切っておく。

```shell:ターミナル

% git checkout -b Build_the_environment

% git branch

```


## .gitignore

環境変数を管理する.envファイルをGit追跡を除外させます。

```sh:.gitignore

.env

```


## .env

.envでは、docker-composeに渡す環境変数の値を入力します。

ここでは、下記の意図で定めています。

|  | 環境変数 | 用途 |
|:-:|:-:|:-:|
| 1 | WORKDIR | back/frontコンテナの作業ディレクトリ名 |
| 2 | MYSQL_ROOT_PASSWORD | ・mysqlコンテナに登録するパスワード<br>・railsコンテナがmysqlコンテナに接続時に用いるパスワード |


```sh:.env

WORKDIR=app
MYSQL_ROOT_PASSWORD=mypassword

```


## docker-compose.yml

次に、複数コンテナをまとめるdocker-compose.ymlを編集します。

MySQLコンテナ/Railsコンテナを先に設定します。
(Vueコンテナは次章で設定予定)

.envファイルから値を受け取って環境変数に登録されていることを
念頭に置きながら理解していきます。

以下の通り、コメントで補足しています。


```yml:docker-compose.yml

# https://docs.docker.com/compose/compose-file/compose-versioning/
version: '3.8'

services:

  db:
    # DockerHubにあるmysql8.0のイメージを使用
    image: mysql:8.0.28
    # MySQL8.0以降の認証方式に元に戻す設定
    command: --default-authentication-plugin=mysql_native_password
    # dbディレクトリをホスト側に作成してコンテナ側と同期
    volumes: 
      - "./db:/var/lib/mysql"
    # .envの環境変数を展開してMYSQLコンテナのパスワードを登録
    environment:
      MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD

  backend:
    # ./backendにあるDockerfiｌｅをもとにイメージを生成する
    build:
      context: ./backend
      # .envの環境変数を展開して、WORKDIRという引数を登録し、backend/Dockerfileに渡す
      args:
        WORKDIR: $WORKDIR
    # ホストのRailsファイルとBack用のコンテナを同期
    volumes:
      - "./backend:/$WORKDIR"
    # .envの環境変数を展開して、Railsの環境変数MYSQL_ROOT_PASSWORDを登録
    environment:
      MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD
    # ホストの8000番ポートをコンテナの3000番ポートに繋ぐ
    ports:
      - "8000:3000"
    # dbコンテナ起動後にbackendコンテナを起動
    depends_on:
      - db

```

なお、環境変数が正しく展開されているかは
下記コマンドでチェックできますので、確認しておきましょう。

```shell:ターミナル

% docker-compose config

```


## Gemfile/Gemfile.lock

GemfileとGemfile.lockの２つをbackendディレクトリに用意します。

Gemfileはrails newコマンドで用いるファイルです。
コマンド実行後はファイルは上書きされてrails構築に必要なGem一式が揃います。
ここではバージョンは6.0.4で指定しています。

Gemfile.lockはGemライブラリのバージョン管理を行うものです。
bundle installコマンド実行後にインストールされたGemを
自動的に管理してくれるものなので、個人では何も編集しません。


```shell:Gemfile

source 'https://rubygems.org'
gem 'rails', '~> 6.0.4'

```

```shell:Gemfile.lock
## 何も記入しない
```

## Dockerfile

続いて、RailsコンテナのイメージとなるDockerfileです。

同様にコメントで補足します。


```dockerfile:backend/Dockerfile

# DockerHubから軽量のalpineイメージを選択する
FROM ruby:2.7.5-alpine

# docker-compose.ymlからWORKDIRの値を受け取る
ARG WORKDIR

# パッケージを永続使用/一時的使用の2つの変数にまとめる
ARG RUNTIME_PACKAGES="bash imagemagick nodejs yarn tzdata mysql-dev mysql-client git　less"
ARG DEV_PACKAGES="build-base curl-dev"

# docker-composeで受け取ったWORKDIRを展開して、RailsコンテナのHOMEという環境変数を設定する(ホームディレクトリを設定)
ENV HOME=/${WORKDIR}

# HOMEを展開して、WORKDIRを設定する(作業ディレクトリをホームディレクトリと統一する)
WORKDIR ${HOME}

# ホストにあるGemfileとGemfile.lockをコンテナにコピーする
COPY Gemfile* ./

# パッケージのインストール　&　削除
RUN apk update && \
    apk upgrade && \
    apk add --no-cache ${RUNTIME_PACKAGES} && \
    apk add --virtual build-dependencies --no-cache ${DEV_PACKAGES} && \
    bundle install -j4 && \
    apk del build-dependencies

# ホストのファイル一式をコンテナにコピーする
COPY . ./

# CMD実行前にentrypoint.shを通す
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# 開発用サーバーの実行
CMD ["rails", "server", "-b", "0.0.0.0"]

```


## entrypoint.sh

entrypoint.shはコンテナ生成前に実行するスクリプトです。

サーバー起動前にエラー発生の可能性であるプロセスを削除します。

処理が終了後は、DockerfileのCMDに戻ります。

```sh:backend/entrypoint.sh

#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

```


## Railsプロジェクトを作成する

では、準備は整いましたので、Railsプロジェクトを作成して
開発用サーバーの起動まで確認しましょう。


```zsh:ターミナル

# イメージを生成する
% docker-compose build

# backendコンテナを起動させてRailsアプリを生成する
% docker-compose run --rm backend rails new . -f -B -d mysql --api --skip-test --skip-turbolinks

### rails new オプション補足 ###
# -f　　　既にあるファイルを上書き(Gemfile/Gemfile.lock)
# -B　　　bundle installコマンドを実行しない
# -d　　　データベースを指定(デフォルトはsqlite)
# --api　　　APIモードで作成する
# --skip-test　　　minitestを作成しない
# --skip-turbolinks　　　turbolinksを作成しない

```

ターミナルで上記コマンド実行後には、Railsアプリに関わるファイル一式が自動生成されているはずです。

同時にGemfileは中身が上書きされています。

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

ruby '2.7.5'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
gem 'rails', '~> 6.0.4', '>= 6.0.4.4'
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.4.4'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

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]
end

group :development do
  gem 'listen', '~> 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

```

## database.yml

ここまででプロジェクトは作成されましたが、
RailsからMySQLへの接続設定が不十分な状態です。

そこで、DB接続に関わるdatabase.ymlを編集します。

具体的に言うと、
Railsコンテナが`rais db:*** コマンド`実行時に
MySQLコンテナに接続する為に用いる設定です。

なお、MySQLのパスワードは秘匿情報のために
`.env -> docker-compose.yml -> railsコンテナの環境変数`の順で
値を橋渡しています。

最終的に環境変数MYSQL_ROOT_PASSWORDを展開して
passwordに設定しています。

```yml:backend/config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  socket: /tmp/mysql.sock
  # 接続先を別コンテナのdbに設定
  host: db
  # MySQLのrootユーザーを使用
  username: root
  # コンテナ内の環境変数を展開した値をpasswordに設定
  password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>


development:
  <<: *default
  database: プロジェクト名_development

```

## 動作検証

では、Railsサーバーが起動するか検証してみましょう。

```shell:ターミナル

# 上書きされたGemfileをもとにイメージを改めて生成する
% docker-compose build

# backendコンテナを起動させて新規データベースを作成する
% docker-compose run --rm backend rails db:create

# バックグラウンドでRailサーバーを起動させる
% docker-compose up

```

ここまでで、ブラウザに`http://localhost:8000`と入力して
動作を確認します。

![スクリーンショット 2022-02-04 16.56.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1380401/e5c48353-9463-6a23-d743-6188a8652ed7.png)

上記のように開発サーバーの画面が表示されていれば成功です。



# 3. VueのDocker環境を構築する

では、次はVueの環境を構築します。

編集するファイルはdocker-compose.ymlとDockerfileです。


## docker-compose.yml

以下の通り、コメントで説明しています。

```yml:docker-compose.yml

--以下を追加する--

  frontend:
    # frontendにあるDockerfileをもとにイメージを生成する
    build:
      context: ./frontend
      # .envの環境変数を展開して、WORKDIRという引数を登録し、frontend/Dockerfileに渡す
      args:
        WORKDIR: $WORKDIR
    # frontendディレクトリにあるホストのファイルとコンテナを同期
    volumes:
      - "./frontend:/$WORKDIR"
    # ホストの3000番ポートをコンテナの3000番ポートに繋ぐ
    ports:
      - "3000:3000"
    # backendコンテナ起動後にfrontendコンテナを起動
    depends_on:
      - backend

```



## Dockerfile

同様にコメントで解説しています。

```dockerfile:frontend/Dockerfile

# nodeのstable版を公式ページから確認し、DockerHubからalpineベースのものを指定
FROM node:16.13.2-alpine

# docker-compose.ymlからWORKDIRの値を受け取る
ARG WORKDIR

# コンテナのホームディレクトリを設定する
ENV HOME=/${WORKDIR}

# コマンド実行する作業ディレクトリの設定する
WORKDIR ${HOME}

# Alpineのリポジトリから最新インデックスを取得し、npm自体も最新版に更新する
RUN apk update && npm install -g npm

# Viteで開発サーバーを起動する
CMD ["npm", "run", "dev"]

```

## Vueプロジェクトを作成する

では準備が整いましたので、Viteを用いてプロジェクトを作成しましょう。

```shell:ターミナル

# イメージの生成
% docker-compose build

# 生成したイメージをもとにashシェルでコンテナに接続する
% docker-compose run frontend ash

```

以降は、frontendコンテナ内でコマンドを実行します。

```shell:ターミナル（frontendコンテナ）

# プロジェクトを生成する
% npm init vite@latest

　↓

#　質問に答える
Need to install the following packages:
  create-vite@latest
Ok to proceed? (y) y
✔ Project name: … vite-project
✔ Select a framework: › vue
✔ Select a variant: › vue

Scaffolding project in /app/vite-project...

Done. Now run:

  cd vite-project
  npm install
  npm run dev

# コンテナ内のディレクトリ/ファイルを確認
% ls -a

# 生成されたファイル群を移動させて、要らないvite-projectディレクトリを削除
% mv vite-project/* /app/
% mv vite-project/.gitignore /app/
% mv vite-project/.vscode /app/
% rmdir vite-project

# package.jsonをもとにライブラリをインストール
% npm install

# frontendコンテナから抜ける
% exit

```

このままだと、ブラウザからコンテナにアクセスできないため
Viteの設定ファイルを編集します。

はじめは、当設定を行わずにサーバーを起動させたのですが
Network: use `--host` to exposeというメッセージが
Vueサーバー側のログに出ていたので、下記記事を参考に編集しました。

https://choice-site.com/2021/06/24/docker%e3%81%abvite%e3%82%92%e5%85%a5%e3%82%8c%e3%81%a6vue%e3%81%a7%e9%96%8b%e7%99%ba%e3%81%97%e3%82%88%e3%81%86%e3%81%a8%e3%81%97%e3%81%9f%e3%81%a8%e3%81%8dlocalhost%e3%81%ab%e7%b9%8b%e3%81%8c/


```js:frontend/vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  // 追加
  server:{
    host: true
  }
})

```

## 動作検証

それでは、コンテナを起動させましょう。

```shell:ターミナル

# docker-composeでコンテナを起動させる
% docker-compose up 


```

ブラウザに`http://localhost:3000/`を入力して
下記画面が表示されれば成功です。

![スクリーンショット 2022-02-05 7.54.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1380401/306eaff8-71d1-f063-baef-3fc68f35fbb2.png)

これで開発環境の構築が終わりです！


# CSSフレームワーク

公式ページの手順に沿って作業を行えば、エラーなく適用できました。

https://tailwindcss.com/docs/guides/vite


# Vue公式ライブラリ

いずれもnpmコマンドでインストールしましたが、同じくエラーありませんでした。

https://next.router.vuejs.org/installation.html

https://next.vuex.vuejs.org/ja/installation.html

src以下のディレクトリ構造は、VueCLIを参考に下記としました。

```shell:ターミナル

% tree frontend/src 
frontend/src
├── App.vue
├── main.js
├── assets
│   └── index.css
├── components
│   └── SignupForm.vue
├── router
│   └── index.js
├── store
│   └── index.js
└── views
    ├── Home.vue
    ├── Vuex.vue
    └── Welcome.vue

```


# 参考記事

https://ja.vitejs.dev/

https://zenn.dev/ponta/scraps/157f8b8af6f7a7



# 参考動画

https://www.udemy.com/course/jwt-login-authentication-with-railsapi-nuxtjs/





# 終わりに

少し前にVueCLIでもプロジェクトを生成したんですが
だんだん速度が遅くなってきたので、思い切ってViteに変えました。

ただし、使いこなせるかが心配であります…笑

長い記事となってしまいましたが、
最後までお読み頂き、ありがとうございました。