【Docker】Rails7 API + React（TypeScript）のSPA開発環境を構築する

以下を`clone`させていただき、環境構築していきます。

https://github.com/ohbarye/rails-react-typescript-docker-example

## compose.ymlの設定
```compose.yml
version: '3'
services:
  db:
    container_name: postgres
    image: postgres:15.2
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_HOST_AUTH_METHOD: trust
  backend:
    container_name: backend
    build: ./backend
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ./backend:/myapp/backend
    depends_on:
      - db
  frontend:
    container_name: frontend
    image: "node:18.15.0-alpine"
    user: "node"
    working_dir: /myapp/frontend
    volumes:
      - ./frontend:/myapp/frontend
    command: "yarn dev"
    depends_on:
      - backend
  yaichi:
    container_name: yaichi
    image: mtsmfm/yaichi:1.8.1
    ports:
      - 80:3000
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

```

imageのバージョンだけ変更しています。
V3のリファレンスも見て、記述に誤りが無いか確認済。

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/compose-file-v3/

※nodeの安定版が`18.15.0`なので、npmにてアップデートします(2023/3/23)
```
$ sudo npm install -g n

$ n -V
v9.0.1

$ n --stable
18.15.0

$ sudo n stable
  installing : node-v18.15.0
       mkdir : /usr/local/n/versions/node/18.15.0
       fetch : https://nodejs.org/dist/v18.15.0/node-v18.15.0-darwin-x64.tar.xz
     copying : node/18.15.0
   installed : v18.15.0 (with npm 9.5.0)

$ node -v
v18.15.0
```
## 用語の説明
#### version
docker-composeで使用するバージョンを定義しています。バージョンごとにComposeFileの書き方が異なります。

#### services
アプリケーションを動かすための要素です。以降、ネストして記述していきます。ここでは`db` `backend` `frontend` `yaichi` の4つをserviceとして定義しています。

#### db
DockerHubにアップされている既存のDockerImageを使います。Railsのようなウェブアプリケーションについては、ディレクトリを指定します。

#### container_name
指定したコンテナ名(postgres)が明示できます。

#### image
Dockerコンテナを起動するために必要なコンテナイメージを指定するために使用されます。`postgres:15.2`など、DockerHubを参照にコンテナイメージを指定します。

#### volumes
マウントする設定ファイルのパスを指定します。DockerEngineの管理下にvolumeを確保してそこにファイルを管理します。`- ./tmp/db:/var/lib/postgresql/data`

マウントについては以下を参考にさせていただきました↓

https://zenn.dev/randd/articles/84ac7de7f22800

#### environment
パスワードなど、DBの環境変数設定を指定します。
`POSTGRES_HOST_AUTH_METHOD`が`trust`ならパスワードを設定しなくてもpsqlに接続できます。

#### backend
バックエンドのContainerの内容を記載します。バックエンドのアプリは、DockerfileからImageをbuildするところから始まります。`build: ./backend`

#### command
コマンドを指定できます。Dockerfileよりも優先されます。

#### depends_on
 Docker Compose の各サービスに対して設定できる項目です。 `depends_on`を使うとサービス間の依存関係を指定できます。この場合`backend`を`db`に依存させています。

#### frontend
フロントエンドのContainerの内容を記載します。

#### user
デフォルトログインユーザ(node)を指定します。

#### working_dir
デフォルトの作業用ディレクトリを指定します`/myapp/frontend`

Serviceの設定項目はかなりたくさんあるようなので、今後、他にも使用していきたいと思います。


## Dockerfileの設定(backend)

リファレンスにベストプラクティスの記載があったので目を通します。

https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/dockerfile_best-practices/


```Dockerfile
FROM ruby:3.2.1
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
RUN mkdir -p /myapp/backend
WORKDIR /myapp/backend
COPY Gemfile /myapp/backend/Gemfile
COPY Gemfile.lock /myapp/backend/Gemfile.lock
RUN bundle install
COPY . /myapp/backend

ENV ENTRYKIT_VERSION 0.4.0
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

ENTRYPOINT [ \
  "prehook", "ruby -v", "--", \
  "prehook", "/myapp/backend/prehook", "--"]
```

### FROM
ベースイメージを指定します。

#### RUN 
ベースイメージに対して追加で行う処理です。
`apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs`
apt-getコマンドを使用し、quietモードupdateします。quietモードは進捗状況を表示しません。
同時に、パッケージをインストールしています。

#### WORKDIR 
操作するディレクトリのパスを指定します。

#### COPY 
コンテナに指定したフォルダやファイルをコピーします。

#### ENV 
環境変数を設定します。

以下の記述でEntrykitを使用しています。Entrykitは、コンテナ内のプロセス起動時に便利な軽量initシステムです。Dockerの起動コマンドに Entrykitを使うと、起動時にテンプレートファイルを元に設定ファイルをレンダリングしてくれたり、メインプロセスの前にコマンドを実行できたりします。
```
ENV ENTRYKIT_VERSION 0.4.0
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink
```
詳細は以下の記事を参考にしました。

https://qiita.com/spesnova/items/bae6406bf69d2dc6f88b


#### ENTRYPOINT
コンテナ起動時に何のコマンドを実行するか定義します。
`prehook` を使用するとメインプロセス起動前にコマンドを複数実行できます。
```
ENTRYPOINT [ \
  "prehook", "ruby -v", "--", \
  "prehook", "/myapp/backend/prehook", "--"]
```


## package.jsonの設定
※以下、詳細は別記事を書きます。
```package.json
{
  "name": "frontend",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack-dev-server",
    "test": "jest",
    "typecheck": "tsc -p . --noEmit"
  },
  "devDependencies": {
    "@types/jest": "^29.5.0",
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "css-loader": "^6.7.3",
    "html-webpack-plugin": "^4.5.2",
    "jest": "^29.5.0",
    "style-loader": "^3.3.2",
    "ts-jest": "^29.0.5",
    "ts-loader": "^9.4.2",
    "typescript": "^5.0.2",
    "webpack": "^4.46.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.13.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}
```


## tsconfig.json
```
{
  "compilerOptions": {
    "target": "es2019",
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "react",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  }
}

```

## webpack.config.js
```webpack.config.js
const path = require("path");
const HTMLPlugin = require("html-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: {
          loader: "ts-loader",
          options: {
            transpileOnly: true,
          },
        },
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"]
      },
    ],
  },
  resolve: {
    extensions: [".js", ".ts", ".tsx", ".json", ".mjs", ".wasm"],
  },
  plugins: [
    new HTMLPlugin({
      template: path.join(__dirname, "src/index.html"),
    }),
  ],
  devServer: {
    host: '0.0.0.0',
    port: 3000,
    disableHostCheck: true,
  },
};

```


## 立ち上げ
```
Setup
$ docker-compose run frontend yarn
$ docker-compose run backend bin/rails db:create db:migrate

Start
$ docker-compose up -d

Open frontend
$ open http://localhost:80

Check backend API
$ curl -H 'Host: backend.localhost' http://localhost/greetings/hello
```