Tasuke Hubのロゴ

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

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

Docker環境でViteのHMR(ホットリロード)が効かない問題の解決法

記事のサムネイル

問題の概要:Dockerコンテナ内でViteのHMRが動作しない

Dockerコンテナを使った開発環境でViteを利用している際に、ホットモジュールリプレイスメント(HMR)が機能しないという問題は多くの開発者が直面する課題です。Viteは高速な開発体験を提供するためのツールですが、Dockerコンテナ内で実行する場合、特にHMR機能が正常に動作せず、コードの変更が自動的に反映されないことがあります。

具体的には以下のような症状が現れます:

  1. コードを編集してもブラウザが自動的に更新されない
  2. ターミナルには変更が検出されているメッセージが表示されるのに反映されない
  3. 手動でブラウザをリロードすると変更が反映される
  4. Dockerコンテナを再起動するとコード変更が反映される

この問題はViteのWebSocketベースのHMRシステムとDockerのネットワーク設定の間の非互換性から生じることが多いです。また、ファイルシステムの監視の問題や設定の不備も原因となります。

// 例:React + TypeScriptプロジェクトでのViteの基本設定
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  // デフォルト設定ではDockerコンテナ内でHMRが機能しないことがあります
})

この記事では、DockerコンテナでViteを使用する際に発生するHMR問題を段階的に解決する方法を、具体的な設定例とともに詳しく説明します。これによりコードの変更がリアルタイムでブラウザに反映される快適な開発環境を実現します。

HMRが効かない主な原因とチェックポイント

DockerコンテナでViteのHMRが機能しない問題には、いくつかの典型的な原因があります。効率的に問題を解決するために、次のチェックポイントを順番に確認していきましょう。

1. ホスト名の設定問題

Viteのデフォルト設定では、HMRのWebSocketサーバーはlocalhostでリッスンします。しかし、Dockerコンテナ内ではlocalhostはコンテナ自体を指すため、ホストマシンのブラウザからアクセスできません。

// 問題のある設定
export default defineConfig({
  // ホスト名が明示的に設定されていないか、localhostに設定されている
})

2. ファイル監視の問題

Dockerコンテナ内でのファイル変更監視には、ホストマシンのファイルシステムとコンテナのファイルシステム間のマウント方法が影響します。特にボリュームマウントの設定によっては、ファイル変更イベントが正しく検出されない場合があります。

# docker-compose.ymlの例
services:
  app:
    volumes:
      - .:/app  # このマウント設定がファイル監視に影響することがあります

3. ポートマッピングの不足

ViteのHMRはデフォルトでWebSocketを使用し、アプリケーションが実行されるポートとは別のポートを使用することがあります。このHMR用のポートがDockerで正しくマッピングされていないと、WebSocket接続が確立できません。

# 不完全なポートマッピング
services:
  app:
    ports:
      - "3000:3000"  # アプリケーションポートのみマッピング
      # HMR用のポートマッピングが欠けている

4. CORS (Cross-Origin Resource Sharing) の問題

ブラウザのセキュリティ制約により、WebSocketの接続元と接続先のオリジンが異なる場合、CORS問題が発生することがあります。特にViteの開発サーバーとブラウザの間でこの問題が発生します。

5. ネットワークレイヤーの問題

Docker内のサービス間通信や、Docker NetworkとホストOSのネットワーク間の相互作用によって、WebSocket接続が妨げられることがあります。

# ネットワーク設定の確認コマンド
docker network ls
docker network inspect <network_name>

次のセクションでは、これらの問題を解決するためのVite設定ファイルの正しい構成方法について説明します。適切な設定によって、ほとんどのHMR問題は解決できます。

Vite設定ファイルの正しい構成方法

ViteのHMR問題の多くは、適切な設定ファイルの構成で解決できます。Docker環境で動作するViteプロジェクトのための主要な設定ポイントを説明します。

ホスト名とポートの適切な設定

Docker環境内のViteサーバーが外部からアクセス可能になるように設定する必要があります。

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    // '0.0.0.0'を指定することで、すべてのネットワークインターフェースでリッスンします
    host: '0.0.0.0',
    // HMRの設定
    hmr: {
      // DockerホストのIPを明示的に指定(オプション)
      clientPort: 3000,  // ホストマシンからアクセスするポート(Docker外部から見たポート)
      port: 3000,        // コンテナ内のポート
    }
  }
})

この設定の重要なポイント:

  1. host: '0.0.0.0' はコンテナの全ネットワークインターフェースでリッスンすることを意味し、コンテナ外部からのアクセスを許可します。
  2. clientPort はホストマシンからアクセスする際のポート番号です。これはDockerのポートマッピングと一致させる必要があります。
  3. port はコンテナ内でViteサーバーが使用するポートです。

ファイル監視の設定強化

Dockerボリュームを介したファイル変更の検出を改善するために、追加の設定を行えます:

// vite.config.ts
export default defineConfig({
  // ...他の設定...
  server: {
    watch: {
      // ポーリングを使用したファイル監視
      usePolling: true,
      // ポーリング間隔(ミリ秒)
      interval: 100,
      // 特定のファイルパターンを除外
      ignored: ['**/node_modules/**', '**/dist/**']
    }
  }
})

ポーリングベースの監視は、イベントベースの監視よりもCPU使用率が高くなる可能性がありますが、Docker環境では信頼性が高くなります。

CORS設定の最適化

CORS関連の問題を防ぐため、適切な設定を追加できます:

// vite.config.ts
export default defineConfig({
  // ...他の設定...
  server: {
    cors: true, // CORSを有効化
    strictPort: true, // 指定したポートが使用できない場合はエラーにする
  }
})

環境変数による設定の柔軟化

環境に応じて設定を変更できるようにするには、環境変数を活用します:

// vite.config.ts
export default defineConfig({
  // ...他の設定...
  server: {
    host: process.env.VITE_HOST || '0.0.0.0',
    port: parseInt(process.env.VITE_PORT || '3000'),
    hmr: {
      clientPort: parseInt(process.env.VITE_HMR_PORT || '3000')
    }
  }
})

これにより、Dockerの環境とローカル開発環境で異なる設定を柔軟に使い分けることができます。

React特有の設定(Reactプロジェクトの場合)

React用のViteプロジェクトでは、HMRの動作を最適化するための追加設定もあります:

// vite.config.ts
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      // Reactのファスト・リフレッシュを有効化
      fastRefresh: true, 
    })
  ],
  // ...他の設定...
})

これらの設定をvite.config.tsに適用した後、次のセクションで説明するDockerの設定を適切に構成することで、HMR問題のほとんどを解決できます。

Docker関連の設定修正ポイント

Vite設定に加えて、Docker側の設定も重要です。ここでは、Docker関連の設定で修正すべきポイントについて説明します。

Dockerfileの最適化

ViteアプリケーションのためのDockerfileを適切に設定することが重要です。以下に、開発環境用の最適化されたDockerfileの例を示します:

FROM node:18-alpine

WORKDIR /app

# パッケージ依存関係のレイヤーをキャッシュするために分離します
COPY package.json package-lock.json ./
RUN npm ci

# アプリケーションコードをコピーします
COPY . .

# Viteの開発サーバーを実行します
CMD ["npm", "run", "dev"]

# 代替コマンド:Viteを直接実行する場合
# CMD ["npx", "vite", "--host", "0.0.0.0"]

この設定のポイント:

  1. node:18-alpineのような軽量なイメージを使用すると、ビルド時間とコンテナサイズを削減できます。
  2. 依存関係のインストール部分を分離することで、ビルドキャッシュを効率的に利用できます。
  3. 開発サーバーを起動するコマンドを明示的に指定しています。

docker-compose.ymlの最適化

docker-compose.ymlファイルでは、以下の点に注意して設定します:

version: '3'
services:
  vite-app:
    build: .
    container_name: vite-app
    ports:
      - "3000:3000"  # アプリケーションポート
      # 追加のポートマッピングが必要な場合(Viteが別ポートでHMRを提供する場合)
      - "24678:24678"  # Viteが使用するHMRのWebSocketポート
    volumes:
      # ソースコードをマウント(変更を即座に反映するため)
      - ./:/app
      # node_modulesをコンテナ内にのみ存在させる設定
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - VITE_HOST=0.0.0.0
      - VITE_PORT=3000
      # コンテナ内から見たホストのIPを明示的に設定
      # - VITE_HMR_HOST=host.docker.internal  # Windows/Mac用(Docker Desktopの場合)
    # ファイル変更の監視を最適化するためにinotify設定を調整(Linuxホストの場合)
    # privileged: true

重要なポイント:

  1. ポートマッピングは複数設定する場合があります。Viteは通常、HMR用のWebSocketを別ポート(デフォルトでは24678)で提供することがあります。
  2. ボリュームマウントでは、node_modulesディレクトリをコンテナ内のみに存在させる設定が推奨されます。これにより、ホストとコンテナ間の依存関係の競合を避けられます。
  3. 環境変数を使って、Viteの設定を柔軟に変更できるようにしています。
  4. DockerDesktopを使用している場合は、host.docker.internalを使ってホストマシンを参照できます。

ネットワーク設定の最適化

複数のDockerコンテナ間でViteアプリケーションを実行する場合、特に注意が必要です:

# 複数サービスがある場合のdocker-compose.yml
version: '3'
services:
  vite-app:
    # ...基本設定...
    networks:
      - app-network
    # バックエンドサービスへの接続が必要な場合
    environment:
      - VITE_API_URL=http://api:8080
      
  api:
    # ...バックエンドサービスの設定...
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

この設定により、ViteアプリケーションとAPIサービスが同じネットワーク上で相互に通信できるようになります。

ボリュームマウントの最適化

特にLinuxホスト上でDockerを実行する場合、ファイル監視の問題が発生しやすいです。以下はパフォーマンス最適化のためのボリューム設定例です:

volumes:
  # 一般的な設定
  - ./:/app
  - /app/node_modules
  
  # 代替設定:Linuxホストでのパフォーマンス向上の場合
  # - ./:/app:delegated  # MacOS用
  # - ./:/app:consistent # Linux用(場合による)

この設定で、ファイルシステムの監視の信頼性とパフォーマンスのバランスを取ることができます。

WebSocketによる接続問題の解決

HMRの問題の多くはWebSocketの接続に関連しています。このセクションでは、WebSocket接続に関連する問題を解決するための具体的な方法を説明します。

ブラウザのDevToolsを使った問題診断

まず、問題を適切に診断するために、ブラウザの開発者ツールを使用してWebSocket接続の状態を確認する方法を説明します:

  1. ブラウザ(Chrome/Firefox)で開発者ツールを開きます(F12またはCmd+Option+I)
  2. 「Network」タブを選択し、「WS」(WebSocket)フィルターを有効にします
  3. ページをリロードしてWebSocket接続を観察します

正常なHMR接続の場合、WebSocketの接続が確立され、「connected」状態になります。接続に問題がある場合は、エラーメッセージが表示されるか、接続試行が繰り返し失敗します。

WebSocket接続URLの確認と修正

Viteは開発中にコンソールにHMRのWebSocket URLを出力します。このURLが正しいネットワーク設定を指しているか確認しましょう:

[vite] connecting to HMR server...
[vite] connected.

問題がある場合、WebSocketのURLが localhost や誤ったIPアドレスを参照している可能性があります。以下の修正が必要です:

// vite.config.ts
export default defineConfig({
  server: {
    hmr: {
      // 明示的にホスト名とプロトコルを指定
      host: '0.0.0.0',
      // 必要に応じてクライアント側から見えるポートを指定
      clientPort: 3000,
      // HTTPSを使用している場合
      protocol: 'wss' // WebSocket Secure
    }
  }
})

プロキシ環境での設定

プロキシやリバースプロキシ(Nginx、Traefik等)を使用している環境では、WebSocketの接続に追加の設定が必要なことがあります:

// vite.config.ts - プロキシ環境向け設定
export default defineConfig({
  server: {
    hmr: {
      // プロキシのパスを指定
      path: '/hmr-websocket',
      // プロキシの背後でViteを実行する場合の設定
      proxy: 'wss://your-domain.com/hmr-websocket'
    },
    proxy: {
      // APIリクエストのプロキシ設定(必要に応じて)
      '/api': {
        target: 'http://api-server:8080',
        changeOrigin: true,
        ws: true // WebSocketも転送
      }
    }
  }
})

CORS関連の問題の解決

WebSocket接続時にCORSエラーが発生する場合、以下の設定が有効です:

// vite.config.ts
export default defineConfig({
  server: {
    // CORS設定を緩和
    cors: {
      origin: '*',
      methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
      preflightContinue: false,
      optionsSuccessStatus: 204
    }
  }
})

WebSocketのフォールバックモード

通常のWebSocket接続が機能しない環境では、Viteのフォールバックオプションを試すことができます:

// vite.config.ts
export default defineConfig({
  server: {
    hmr: {
      // ロングポーリングをフォールバックとして使用
      overlay: false,
      // WebSocketが機能しない場合のフォールバック
      // 注意:パフォーマンスは低下します
      timeout: 30000, // タイムアウトを増やす(デフォルトは30秒)
    }
  }
})

セキュリティ設定によるブロックの解決

企業ネットワークやファイアウォールによってWebSocket接続がブロックされている場合は、以下の対策が有効です:

// vite.config.ts
export default defineConfig({
  server: {
    // HTTPSで通信する場合の設定
    https: {
      key: fs.readFileSync('./certs/key.pem'),
      cert: fs.readFileSync('./certs/cert.pem')
    },
    hmr: {
      // セキュアなWebSocketを使用
      protocol: 'wss',
      // 標準的なHTTPSポートを使用(社内ネットワークでより許可される可能性がある)
      port: 443
    }
  }
})

これらの設定を適用することで、WebSocket接続に関連する多くの問題を解決できます。次のセクションでは、これまでの設定を踏まえた実践的なトラブルシューティングの例と確認方法を紹介します。

トラブルシューティングの実践例と確認方法

これまでの設定を適用しても問題が解決しない場合のトラブルシューティング手順と確認方法を紹介します。具体的な問題シナリオとその解決方法を順に説明します。

シナリオ1: ファイル変更が検出されない

症状: ファイルを変更しても、Viteのコンソールに変更検出のメッセージが表示されない。

確認手順:

  1. コンテナ内でファイル監視が機能しているか確認します:
# コンテナ内でコマンドを実行
docker exec -it vite-app sh

# コンテナ内で以下を実行
ls -la /app
touch /app/test-file.txt
  1. ホスト側でファイルが作成されたことを確認します。
  2. vite.config.tsのファイル監視設定を確認します:
// ファイル監視設定を確認
server: {
  watch: {
    usePolling: true,
    interval: 100
  }
}

解決策:

// vite.config.ts - ファイル監視の設定強化
export default defineConfig({
  server: {
    watch: {
      usePolling: true,
      interval: 100, // 短い間隔でポーリング
      ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**']
    }
  }
})
# docker-compose.yml - ボリュームマウント設定の最適化
services:
  vite-app:
    volumes:
      - type: bind
        source: .
        target: /app
      - /app/node_modules
    environment:
      - CHOKIDAR_USEPOLLING=true # Chokidarにポーリングを強制

シナリオ2: ファイル変更は検出されるがブラウザが更新されない

症状: Viteのコンソールには変更検出のメッセージが表示されるが、ブラウザが自動更新されない。

確認手順:

  1. ブラウザのDevToolsでWebSocket接続を確認します(Network > WS)。
  2. Viteのコンソール出力からHMR接続のURLを確認します。
  3. ログにWebSocket接続エラーがないか確認します。

解決策:

// vite.config.ts - WebSocket設定の修正
export default defineConfig({
  server: {
    host: '0.0.0.0',
    hmr: {
      // 明示的に設定
      host: '0.0.0.0',
      port: 3000,
      // クライアントから見たポートを指定
      clientPort: 3000
    }
  }
})

シナリオ3: CORSエラーが発生する

症状: ブラウザのコンソールにCORSエラーが表示される。

確認手順:

  1. ブラウザのDevToolsでCORS関連のエラーメッセージを確認します。
  2. WebSocketの接続URL(wss://やws://で始まる)を確認します。

解決策:

// vite.config.ts - CORS設定の緩和
export default defineConfig({
  server: {
    cors: {
      origin: '*',
      methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
    },
    hmr: {
      // ブラウザからアクセスするURLのホスト名と一致させる
      host: location.hostname,
      protocol: location.protocol === 'https:' ? 'wss' : 'ws'
    }
  }
})

シナリオ4: 特定のフロントエンドフレームワークでHMRが機能しない

症状: React、Vue、Svelteなど特定のフレームワークでHMRが機能しない。

確認手順:

  1. フレームワーク特有のHMR設定を確認します。
  2. フレームワーク用のViteプラグイン設定を確認します。

解決策 (React用の例):

// vite.config.ts - React用の設定
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      fastRefresh: true,
      // Reactコンポーネントの更新時に状態を保持
      include: '**/*.{jsx,tsx}',
    })
  ],
  server: {
    hmr: {
      // 他のHMR設定...
    }
  }
})

シナリオ5: Linux環境特有のファイル監視問題

症状: Linux環境でホットリロードが動作しない、または遅延が大きい。

確認手順:

  1. ホストOSがLinuxかどうか確認します。
  2. inotifyのリソース上限を確認します。
# inotify監視の上限を確認
cat /proc/sys/fs/inotify/max_user_watches

解決策:

  1. inotifyの上限を増やします(ホストマシンで実行):
# 一時的に変更
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

# 永続的に変更(システム再起動後も有効)
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
  1. docker-compose.ymlでマウントオプションを最適化します:
volumes:
  - ./:/app:cached  # より高速なマウントオプション

シナリオ6: 複数コンテナ環境での通信問題

症状: フロントエンドとバックエンドが別々のコンテナにある場合に、HMRやAPIリクエストが失敗する。

確認手順:

  1. コンテナ間のネットワーク設定を確認します。
  2. APIリクエストのURLがコンテナ名を使用しているか確認します。

解決策:

# docker-compose.yml - ネットワーク設定の最適化
version: '3'
services:
  frontend:
    # ...他の設定...
    networks:
      - app-network
    environment:
      - VITE_API_URL=http://backend:8080
      
  backend:
    # ...他の設定...
    networks:
      - app-network

networks:
  app-network:
    driver: bridge
// vite.config.ts - プロキシ設定
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://backend:8080',
        changeOrigin: true,
        ws: true
      }
    }
  }
})

最終確認方法

全ての設定を適用した後、以下の手順でHMRが正常に動作しているか確認します:

  1. Dockerコンテナを再起動します:
docker-compose down
docker-compose up --build
  1. ブラウザでアプリケーションにアクセスします。

  2. ブラウザのDevToolsを開き、「Console」タブと「Network > WS」タブを確認します。

  3. ソースファイルを編集して保存し、以下を確認します:

    • ターミナルにファイル変更の検出メッセージが表示されるか
    • ブラウザが自動的に更新されるか
    • コンソールに「[vite] hot updated: [ファイル名]」のようなメッセージが表示されるか
  4. 問題が解決しない場合は、Viteのデバッグログを有効にします:

# package.jsonのスクリプトを変更
"scripts": {
  "dev": "vite --debug"
}

これで、Docker環境でのVite HMR問題のほとんどを解決できるはずです。設定を正しく行えば、効率的な開発体験を実現できます。複雑なプロジェクトでは、これらの設定を組み合わせて使用することで、最適な開発環境を構築しましょう。

TH

Tasuke Hub管理人

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

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

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

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

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

おすすめ記事

おすすめコンテンツ