Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

Docker環境でTypeScriptのホットリロードが効かない時の解決策

記事のサムネイル

Docker環境でホットリロードが効かない原因

Docker環境でTypeScriptアプリケーションを開発していると、コードを変更してもホットリロードが効かず、変更が反映されないという問題に直面することがあります。この問題は非常に厄介で、開発効率を著しく低下させます。

主な原因として以下が考えられます:

  1. ファイルシステムの監視問題: Dockerコンテナ内でのファイル変更検知の仕組みが正しく機能していない
  2. ボリュームマウントの設定ミス: ローカルのファイルがコンテナに正しくマウントされていない
  3. nodemonやts-node-devなどの設定不足: 監視対象や監視オプションが適切に設定されていない
  4. ホストとコンテナの環境の違い: 特にWindowsやmacOSでの開発時に発生しやすい問題

具体的には、TypeScriptファイルを変更しても自動的にトランスパイルが行われず、アプリケーションに変更が反映されないという現象が起こります。

// このファイルを変更しても反映されない例
const greeting = (name: string): string => {
  return `Hello, ${name}!`;
};

console.log(greeting("World"));

この問題を解決するには、Docker環境の設定とTypeScriptの開発環境の両面から対処する必要があります。

ボリュームマウントの設定を見直す

Docker環境でホットリロードが効かない最も一般的な原因は、ボリュームマウントの設定にあります。特にTypeScriptプロジェクトでは、ソースコードの変更が正しくコンテナに反映されていることが重要です。

正しいボリュームマウント設定

docker-compose.ymlファイルでのボリュームマウントを確認しましょう:

version: '3'
services:
  app:
    build: .
    volumes:
      # 間違った設定例(サブディレクトリのみのマウント)
      # - ./src:/app/src
      
      # 正しい設定例(プロジェクト全体をマウント)
      - .:/app
      # node_modulesはホストとコンテナで分離する(重要)
      - /app/node_modules
    ports:
      - "3000:3000"

ここで重要なポイントは以下の2つです:

  1. プロジェクト全体をマウントする: .:/appのように、プロジェクト全体をマウントすることで、設定ファイルなども含めて正しく監視されます。
  2. node_modulesを除外する: /app/node_modulesという記述で、コンテナ内のnode_modulesディレクトリはホストと共有されなくなります。これにより、ホストとコンテナのモジュールの不整合を防ぎます。

一般的な間違い

よくある間違いとしては以下のパターンがあります:

volumes:
  # 間違い1: srcディレクトリだけをマウント
  - ./src:/app/src
  
  # 間違い2: node_modulesを除外していない
  - .:/app
  
  # 間違い3: 相対パスの使用ミス
  - ../project:/app

これらの設定ミスはホットリロードの問題を引き起こすだけでなく、依存関係の解決やビルドプロセスにも影響します。

ファイル監視の設定を最適化する

TypeScriptプロジェクトでのホットリロードを確実に機能させるためには、ファイル監視の設定を最適化することが重要です。特にnodemonts-node-devなどのツールを使う場合は、設定を見直しましょう。

nodemonの設定最適化

nodemon.jsonファイルを使って設定を最適化できます:

{
  "watch": ["src/**/*.ts"],
  "ignore": ["src/**/*.spec.ts", "node_modules"],
  "ext": "ts,json",
  "exec": "ts-node ./src/index.ts",
  "legacyWatch": true,
  "delay": "1000",
  "poll": true
}

特に重要な設定項目:

  • legacyWatch: trueに設定することで、古いファイル監視システムを使用し、特にDockerでの互換性問題を解決することがあります
  • poll: trueに設定することで、ファイルシステムイベントに依存せず、定期的なポーリングでファイル変更を検知します(Docker環境で特に有効)
  • delay: 変更検知からの遅延時間を設定し、多数の変更が短時間に行われた場合の重複再起動を防ぎます

ts-node-devの設定

ts-node-devを使用している場合は、以下のようにpackage.jsonに設定できます:

{
  "scripts": {
    "start": "ts-node-dev --respawn --transpile-only --poll --clear src/index.ts"
  }
}

オプションの説明:

  • --respawn: プロセスを再起動して変更を適用
  • --transpile-only: 型チェックをスキップして高速化
  • --poll: ポーリング方式でファイル変更を監視
  • --clear: 再起動時にコンソールをクリア

WebpackでのDevServerの設定

Webpackを使用している場合は、webpack.config.jsで以下のように設定します:

module.exports = {
  // ...他の設定
  devServer: {
    watchOptions: {
      poll: 1000, // 1秒ごとにチェック
      ignored: /node_modules/,
      aggregateTimeout: 300 // 変更後の遅延
    }
  }
};

このような設定により、Docker環境でもファイルの変更が効率的に検知され、ホットリロードが正常に機能するようになります。

ホットリロードを効かせるDockerfile設定例

Docker環境でTypeScriptのホットリロードを確実に動作させるためには、Dockerfileの適切な設定も重要です。

基本的なTypeScript用Dockerfile

FROM node:18-alpine

WORKDIR /app

# パッケージをインストールする前にpackage.jsonとpackage-lock.jsonをコピー
COPY package*.json ./

# 依存関係をインストール
RUN npm ci

# プロジェクトファイルをコピー(docker-compose.ymlのボリュームマウントで上書きされます)
COPY . .

# 開発モードでアプリケーションを起動(ホットリロード対応)
CMD ["npm", "run", "dev"]

改良版:ホットリロードに最適化したDockerfile

FROM node:18-alpine

# タイムゾーンを設定(オプション)
ENV TZ=Asia/Tokyo

WORKDIR /app

# パッケージをインストールする前にpackage.jsonとpackage-lock.jsonをコピー
COPY package*.json ./

# ノードモジュールのインストール(--legacy-peer-depsフラグで互換性問題を解決)
RUN npm ci --legacy-peer-deps

# nodemonをグローバルにインストール
RUN npm install -g nodemon

# TypeScriptをグローバルにインストール
RUN npm install -g typescript

# tsconfig.jsonをコピー
COPY tsconfig.json ./

# コンテナの起動時にnodemonを使用
CMD ["nodemon", "--config", "nodemon.json"]

このDockerfileでは以下の点が重要です:

  1. devDependenciesを含めたインストール: TypeScriptやその他の開発ツールが必要なため
  2. グローバルなツールのインストール: コンテナ内でnodemonやTypeScriptを直接利用可能に
  3. 適切なCMD命令: ホットリロードをサポートするコマンドを指定

Next.js用の最適化されたDockerfile例

Next.jsプロジェクトの場合は少し異なる設定が必要です:

FROM node:18-alpine

WORKDIR /app

# 依存関係をコピー
COPY package*.json ./

# 依存関係をインストール
RUN npm ci

# 開発モードで起動し、ホットリロードを有効化
CMD ["npm", "run", "dev", "--", "-H", "0.0.0.0"]

Next.jsの場合は、-H 0.0.0.0オプションを使うことで、コンテナ外からのアクセスを許可し、適切にホットリロードが機能するようになります。

docker-compose.ymlの設定ポイント

Docker ComposeでTypeScriptアプリケーションのホットリロードを有効にするには、いくつかの重要な設定ポイントがあります。

完全なdocker-compose.yml例

version: '3'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    environment:
      # 環境変数でホットリロードを明示的に有効化
      - CHOKIDAR_USEPOLLING=true
      - NODE_ENV=development
      # TypeScriptのファイル監視やトランスパイルに関連する設定
      - TS_NODE_PROJECT=tsconfig.json
    # コンテナ内のファイル変更監視のためのinotify limitsを増やす
    # Linuxホスト限定の設定
    sysctls:
      - fs.inotify.max_user_watches=524288
    # コンテナ再起動ポリシー
    restart: unless-stopped
    # コマンド上書き(オプション)
    command: npm run dev

主要な設定ポイント

  1. 環境変数の設定:

    • CHOKIDAR_USEPOLLING=true: ファイル監視にポーリング方式を使用(多くのNode.jsベースの開発サーバーで有効)
    • NODE_ENV=development: 開発モードを明示的に有効化
  2. sysctls設定:

    • fs.inotify.max_user_watches: Linuxベースのホストで使用する場合、ファイル監視の制限を引き上げる設定
  3. restart設定:

    • unless-stopped: 不意のエラーでコンテナが停止した場合に自動的に再起動

docker-compose.ymlを使った実践例

Expressベースの簡単なTypeScriptアプリを例にした完全な設定:

version: '3'

services:
  typescript-express-app:
    build:
      context: .
      dockerfile: Dockerfile.dev  # 開発用のDockerfileを指定
    container_name: typescript-express-dev
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
      # Hot Module Replacement用のポート(Webpackなどを使用する場合)
      - "8080:8080"
    environment:
      - CHOKIDAR_USEPOLLING=true
      - NODE_ENV=development
      - TS_NODE_PROJECT=tsconfig.json
    # ログ設定(オプション)
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    # ヘルスチェック(オプション)
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

この例では、Express.jsアプリケーションに典型的なポート3000に加えて、HMR(Hot Module Replacement)用のポート8080も公開しています。また、コンテナのログやヘルスチェックの設定も含めています。

特殊なケースの対処法

一般的な設定を調整しても解決しない場合や、特殊な環境で開発している場合には、以下の対処法が有効です。

macOS特有の問題への対処

macOSでDocker Desktopを使用している場合、ファイル変更検知が特に遅い場合があります。この場合、以下の対策が効果的です:

  1. Docker Desktopの設定調整:

    • Docker Desktopの「Preferences」→「Resources」→「File Sharing」で監視対象フォルダが正しく追加されていることを確認
    • 「Advanced」タブでリソース割り当て(CPUやメモリ)を増やす
  2. 代替ソリューション - docker-sync:

# インストール
gem install docker-sync

# 設定ファイル (docker-sync.yml) の例
version: '2'
syncs:
  app-sync:
    src: './'
    sync_strategy: 'native_osx'
    sync_excludes: ['node_modules', 'dist', '.git']
# docker-compose.yml での使用例
version: '3'
services:
  app:
    # ... 他の設定 ...
    volumes:
      - app-sync:/app
      - /app/node_modules
volumes:
  app-sync:
    external: true

実行するには:

# 同期を開始
docker-sync start

# docker-composeと一緒に起動
docker-sync-stack start

Windows特有の問題への対処

Windows環境では、WSL2を使用することを強く推奨します。WSL2でDockerを使用すると、ファイル監視の問題が大幅に改善されます。

  1. WSL2の設定:

    • プロジェクトはWSL2のファイルシステム内に配置する
    • VS CodeでWSL拡張機能を使用して開発する
  2. WSLのファイルパフォーマンス向上:

WSL2の.wslconfigファイル(C:\Users\<YourUsername>\.wslconfig)を調整:

[wsl2]
memory=8GB
processors=4
localhostForwarding=true
kernelCommandLine = "fs.inotify.max_user_watches=524288"

モノレポ環境での対処法

大規模なモノレポプロジェクトでホットリロードの問題に直面した場合:

# 大規模プロジェクト用のdocker-compose.yml
version: '3'
services:
  app:
    # ... 他の設定 ...
    volumes:
      # 必要なサブディレクトリだけをマウント
      - ./packages/my-app:/app/packages/my-app
      - ./shared:/app/shared
      - /app/node_modules
      - /app/packages/my-app/node_modules
    environment:
      # 個別プロジェクトに焦点を当てる
      - APP_ROOT=/app/packages/my-app

その他の対処法とチェックリスト

解決しない場合の最終手段:

  1. watch modeのデバッグ有効化:

    DEBUG=*,-not_this node --inspect=0.0.0.0:9229 ./node_modules/.bin/nodemon src/index.ts
  2. ライブラリ更新の確認:

    # 最新のts-node-devなどをインストール
    npm install --save-dev ts-node-dev@latest nodemon@latest
  3. システム設定の確認:

    # Linuxの場合:ファイル監視上限の設定を確認
    cat /proc/sys/fs/inotify/max_user_watches
    
    # 変更する場合
    echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
    sudo sysctl -p

これらの対処法を適切に組み合わせることで、ほとんどのDocker環境でTypeScriptのホットリロードの問題を解決できます。特に重要なのは、ご自身の開発環境(macOS, Windows, Linux)に合わせた最適な設定を選ぶことです。

TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

おすすめ記事

おすすめコンテンツ