😵

[未解決] NestJS Docker HMR

2024/09/27に公開
2

NestJS Docker HMR is not working

問題点

NestJSをDockerに載せて動かす時、HMRやlive reloadなどが動かない

解決策

まだわかっていない。
この件についてまるで分からないので分かる方教えて頂けると幸いです。

内容

Dockerfile

Dockerfile
FROM node:20.12.0
RUN npm i -g @nestjs/cli
WORKDIR /app

COPY package.json package-lock.json ./

RUN npm ci

COPY . .

CMD [ "npm", "run", "docker:init" ]

docker-compose.yml

docker-compose.yml
version: '3.8'
services:
  api:
    container_name: api
    build: .
    tty: true
    ports:
      - '3000:3000'
    volumes:
      - type: bind
        source: .
        target: /api
    env_file:
      - config/.env
    depends_on:
      mysql:
        condition: service_healthy
  mysql:
    container_name: mysql
    image: mysql:8
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=caching_sha2_password
    ports:
      - '3306:3306'
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: database
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    volumes:
      - ./mysql_data:/var/lib/mysql
      - ./mysql-init.sql:/docker-entrypoint-initdb.d/mysql-init.sql
    healthcheck:
      test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost']
      interval: 10s
      timeout: 5s
      retries: 5

.dockerignore

.dockerignore
node_modules
dist
mysql_data

package.json

package.json
"script": {
  "docker:init": "npm run prisma:migrate && npm run prisma:generate && npm run start:dev"
]

試したこと

公式に記載されていること

hot-reload in NestJS Doc

  1. 必要パッケージのインストール
terminal
npm i --save-dev webpack-node-externals run-script-webpack-plugin webpack
  1. webpack-hmr.config.jsの作成
webpack-hmr.config.js
const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');

module.exports = function (options, webpack) {
  return {
    ...options,
    entry: ['webpack/hot/poll?100', options.entry],
    externals: [
      nodeExternals({
        allowlist: ['webpack/hot/poll?100'],
      }),
    ],
    plugins: [
      ...options.plugins,
      new webpack.HotModuleReplacementPlugin(),
      new webpack.WatchIgnorePlugin({
        paths: [/\.js$/, /\.d\.ts$/],
      }),
      new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false }),
    ],
  };
};

をルートディレクトリに追加

  1. package.jsonに下記を追加
package.json
"start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch"
  1. main.tsの変更
    module.hotの定義が無かった為、@types/webpack-envをインストールした。
    -> 無事、型定義エラーは消えた
main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

  // 下記追記
+  if (module.hot) {
+    module.hot.accept();
+    module.hot.dispose(() => app.close());
  }
}
bootstrap();

この状態で、docker compose upして起動は確認できた。
webpack 5.90.1 compiled successfully in 1851 ms

その他試した事

webpack-hmr.config.js

上記のwebpack-hmr.config.jsを下記の様に変更した

webpack-hmr.config.js
const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');

module.exports = function (options, webpack) {
  return {
    ...options,
    entry: ['webpack/hot/poll?100', options.entry],
    externals: [
      nodeExternals({
        allowlist: ['webpack/hot/poll?100'],
      }),
    ],
    plugins: [
      ...options.plugins,
      new webpack.HotModuleReplacementPlugin(),
      // WatchIgnorePluginは削除、または無視するパスを限定
      new webpack.WatchIgnorePlugin({
-        paths: [/\.js$/, /\.d\.ts$/],
+        paths: [/\.d\.ts$/], // .d.tsファイルのみ無視
      }),
      new RunScriptWebpackPlugin({
        name: options.output.filename,
-       autoRestart: false
+       autoRestart: true, // HMRで再起動するように設定
      }),
    ],
  };
};

docker-compose.yml

volumesの箇所を変更した

docker-compose.yml
volumes:
      - type: bind
        source: .
        target: /api
+      - /api/node_modules
docker-compose.yml
volumes:
      - type: bind
        source: .
        target: /api
+      - .:/app
+      - /api/node_modules

tsconfig.jsonの変更

GitHub Issue

以上のIssueから

tsconfig.json
+ "watchOptions": {
+    "watchFile": "fixedPollingInterval"
+  }

portチェック

main.ts
 await app.listen(3000)
docker-compose.yml
 ports:
      - '3000:3000'

合っているから問題なさそう

推論

webpack-hmr.config.jsなどを導入し起動した際、
webpack 5.90.1 compiled successfully in 1851 ms
の表示と共にAPIリストなど出力されて起動は確認できた。

コードの変更検知->Docker変更認知

変更検知からDocker変更認知の辺りで上手くかみ合ってないと思っている。
何が正しいかもわかっていませんが…

tsc --watchを入れて動かすとまた違うのか?
Dockerも詳しくないし、ここら辺の仕組みがまるで分からない。。。

React Vite on Dockerの環境では、HMRを動かすことに成功したが、NestJSはまるでわからん。
docker-compose.yml

docker-compose.yml
volumes:
      - type: bind
        source: .
        target: /api
+      - .:/app
+      - /api/node_modules

のような感じでvolumesにソースコードを渡してやったら出来たり、

vite.config.ts
export default defineConfig({
  plugins: [react(), vanillaExtractPlugin()],
  server: {
    host: '0.0.0.0',
    port: 3000,
    hmr: true,
    watch: {
      usePolling: true,
    },
  },
})

と記載したら出来た。
host, portは問題ないはず
watch-pollingやhmrの起動辺りのオプションだったりがNestJSにもあると思われる。

Dockerに載せるようになってまるで分からない
Dockerへの理解がまるでないからきっとそこに原因はありそうな気がするけれど。。。

どなたか同様の問題で解決方法を知っていれば教えて頂けると幸いです。

追記

環境を用意してdocker compose up --watchを行ったところ、bcryptのエラーが出た
Error: /app/node_modules/bcrypt/lib/binding/napi-v3/bcrypt_lib.node: invalid ELF header

似たような形できっとdayjsも出てくるのだろうと思いながら進捗はストップ

過去にもこれが出てきて、分からずに他人にヘルプを出したが、今回もそうなりそう。
何が原因で何がだめなのかが全く分からない。

stackoverflow

ここに書いてある原因としてコンパイルアーキテクチャが違うから?

やってみたこと

docker-compose.yml
volumes:
     - type: bind
       source: .
       target: /api
      - .:/app
-      - /api/node_modules

node_modulesを渡すのをやめてみた
結果、相変わらずだった

OS由来やコンパイルアーキテクチャがどうのこうのと言われても全く知らないので、何となく違うんだろうなとしか思えず良い案が出てこない。。。

毎度、綺麗な解決方法を知らないし原因が具体的にどういうものなのかも掴めてないのがつらい。

Discussion

Hiroshi KoyamaHiroshi Koyama

手元で Nest.js アプリの HMR は Docker コンテナーでも動作しています。確認したときの内容を記事にしてありますので、よろしかったら、ご覧ください。

https://zenn.dev/hiro345/articles/20240928_docker_compose_watch

こちらは Ubuntu 22.04 環境なのですが、使われている OS が Windows でも、記事で紹介している Compose Watch 機能で解決しそうな気がします。

rabirabi

コメントありがとうございます!
Watch機能、完全に意識から漏れてました。。。
一度チャレンジしてまた何か進展があれば追記させていただきます!
ご教示いただきありがとうございます。