検証目的
シンプルな興味本位というものが大きいですが、フロントエンドの開発でdevcontainerを使うときにどこにソースコードを配置すべきかを把握したかったため検証いたしました。
devcontainerではリモート側(Dockerコンテナ)とホスト側(Windows or wsl)とファイル同期を行うため、どこにソースコードを配置するかでパフォーマンスに影響が出るのはという仮説の元検証いたしました。
前提:環境
- Windows 11
- Docker Desktop使用せず、ubuntu内のDockerを使用
- wsl2
- Docker v26.1.4
- Container
- node v22
- next.js v14.2.4
ソースコードは以下リポジトリのソースコードを使用。
適当なmaterial-uiの部品を置いたソースコードを300ページ作成して検証しました。
検証時に裏で動いているアプリがどれくらいプロセスを使ってるかの誤差は無視します。
前提:Dockerfileとdocker-compose.ymlの違いを少しだけ説明
- Dockerfile
Dockerfileはコンテナイメージをビルドするための設定ファイルです。
コンテナイメージをどういう構成にするかの設定を記述するファイルです。
コンテナを料理に置き換えると、Dockerfileはレシピにあたります。 - docker-compose.yml
docker-compose.ymlは複数のコンテナをまとめて管理するための設定ファイルです。
複数のコンテナをまとめて管理するための設定を記述するファイルのため、Dockerfileよりも上位の概念です。
そのためDockerfileを指定するのもdocker-compose.ymlです。
コンテナを料理に置き換えると、docker-compose.ymlは献立にあたります。
ざっくりまとめると
Dockerfile = 単独
docker-compose.yml = 複数コンテナ
というイメージです。
以下のソースコードで検証いたしました。
Dockerfile
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
docker-compose.yml
version: "3.9"
services:
next:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/app
- /app/node_modules
ports:
- 3000:3000
environment:
- NODE_ENV=development
tty: true
やっていることはシンプルで、node v22のイメージに既存ディレクトリのソースをコピーし、3000ポートのポートフォワーディングしているだけです。
node_modulesはホスト側とリモート側で同期されないようにvolume箇所に記述しています。(volume trickと言うらしい)
※node_modulesがホスト側とリモート側で共有された場合、例えばnodejsをインストールしているバージョンがホスト側とリモート側で異なる場合に壊れてしまいます。
時間計測結果
今回は3パターン検証いたしました。
①Windows側にソースを置いてDockerを立ちあげる場合
![](https://murakamipeipei.com/wp-content/uploads/2024/06/スクリーンショット-2024-06-30-134947.png)
②wsl2内にソースを置いてDockerを立ちあげる場合
![](https://murakamipeipei.com/wp-content/uploads/2024/06/スクリーンショット-2024-06-30-134805.png)
③Docker内でgit cloneし、container内のみ使用する場合
![](https://murakamipeipei.com/wp-content/uploads/2024/06/スクリーンショット-2024-06-30-134828.png)
検証にはnextjsで良く使用されるコマンドを使って検証いたしました。
各コマンドは以下の前提で検証いたしました。
npm install
はnode_modules
、package-lock.json
を削除した初回に実行npm run build
は.next
フォルダを削除した初回に実行npm run dev
は初回の画面表示までの時間 cacheを使わないように.next
フォルダを削除した初回立ち上げで実行
また、計測にはコマンド実行から終了までを計測するtime
コマンドを使用しました。
以下が実行結果です。
実施コマンド | ①Windows⇔(wsl)⇔container | ②wsl⇔container | ③containerのみ |
---|---|---|---|
npm install | 2m 36.214s | 1m7.595s | 38.208s |
npm run build | 3m35.006s | 42.846s | 41.313s |
npm run dev(ready) | 21s | 1713ms | 1792ms |
npm run dev(compile) | 20.4s | 5s | 2.5s |
「①Windows⇔(wsl)⇔container」時のnpm install
npm run build
は遅すぎて使い物になりませんでした。npm run dev
で立ち上げた際のコンパイルにも時間がかかって重たい印象です。
これは仮説ですが、もしかしたらbuild時に生成する.next
ディレクトリとnpm installに格納されるnode_modules
を同期対象外?(そんなことできる?)にすると一時的にですがパフォーマンス向上するのかなと思いました。
「②wsl⇔container」と「③containerのみ」の差はそこまで違いを感じるほどでもなかったです。
wsl⇔container間の同期は300画面ぐらいのソース量ではそこまで気にしなくても良いのかなと思います。
「③containerのみ」で実際にdockerのみで開発を進める場合はdocker起動用のdevcontainerを使い、その中にソースコードをgit cloneして開発を進める形になると思います。
今回はそこまで用意したくなかったため、同期を取っていないworkspace
ディレクトリ以外の/home/node
のディレクトリ配下に既存のソースコードをコピーして検証しています。
別件
nextjsをdevcontainerで動かす際にhot-reload(編集した内容が即時に反映される)が有効になっていなかったため、next.config.js
を一部編集しています。
/** @type {import('next').NextConfig} */
const nextConfig = {
// NOTE: Windowsのdevcontainerでhot-reloadを有効化する。
webpack: (config, context) => {
config.watchOptions = {
ignored: /node_modules/,
poll: 1000,
aggregateTimeout: 300
}
return config
}
};
export default nextConfig;
計測結果内訳証跡
検証した際の記録がデタラメな数値では無いという気持ちで、一応各実行コマンド実行結果載せます。
①Windows⇔(wsl)⇔container
npm install
root@3afaf11d1b9e:/workspaces/dev-containe
r# time npm install
real 2m36.214s
user 0m42.335s
sys 0m22.077s
npm run build
root@3afaf11d1b9e:/workspaces/dev-container# time npm run build
+ First Load JS shared by all 87.1 kB
├ chunks/7023-d2da6b93d22bcac5.js 31.5 kB
├ chunks/fd9d1056-d0c14db3e40745f5.js 53.6 kB
└ other shared chunks (total) 1.91 kB
○ (Static) prerendered as static content
real 3m35.006s
user 2m7.220s
sys 0m55.877s
npm run dev
root@3afaf11d1b9e:/workspaces/dev-container# time npm run dev
✓ Starting...
✓ Ready in 21s
✓ Compiled /original in 20.4s (1109 modules)
GET /original 200 in 22129ms
②wsl⇔container
npm install
root@c56ad4106a32:/workspaces/nextjs-devcontainer# time npm install
real 1m7.595s
user 0m15.004s
sys 0m7.575s
npm run build
root@7ac08c9ad9b1:/workspaces/nextjs-devcontainer# time npm run build
+ First Load JS shared by all 87.1 kB
├ chunks/7023-d2da6b93d22bcac5.js 31.5 kB
├ chunks/fd9d1056-d0c14db3e40745f5.js 53.6 kB
└ other shared chunks (total) 1.91 kB
○ (Static) prerendered as static content
real 0m42.846s
user 1m24.262s
sys 0m18.460s
npm run dev
root@7ac08c9ad9b1:/workspaces/nextjs-devcontainer# time npm run dev
✓ Starting...
✓ Ready in 1713ms
✓ Compiled / in 4s (544 modules)
GET / 200 in 4193ms
○ Compiling /original ...
✓ Compiled /original in 5s (1109 modules)
GET /original 200 in 5351ms
③containerのみ
npm install
root@7ac08c9ad9b1:/home/node/nextjs-devcontainer-docker# time npm install
real 0m38.208s
user 0m13.762s
sys 0m7.153s
npm run build
root@7ac08c9ad9b1:/home/node/nextjs-devcontainer-docker# time npm run build
+ First Load JS shared by all 87.1 kB
├ chunks/7023-d2da6b93d22bcac5.js 31.5 kB
├ chunks/fd9d1056-d0c14db3e40745f5.js 53.6 kB
└ other shared chunks (total) 1.91 kB
○ (Static) prerendered as static content
real 0m41.313s
user 1m18.697s
sys 0m19.593s
npm run dev
root@7ac08c9ad9b1:/home/node/nextjs-devcontainer-docker# time npm run dev
✓ Starting...
✓ Ready in 1792ms
✓ Compiled / in 3.3s (544 modules)
GET / 200 in 3485ms
✓ Compiled /original in 2.5s (1132 modules)
GET /original 200 in 2752ms
まとめ
フロントエンドでdevcontainerを使う場合は「②wsl⇔(container)」のwslにソースコードを配置するで良いかなと思います。
検証してみて、Windows⇔(wsl) のオーバーへッドが予想以上に大きいなという感想です。
devcontainerを使う際には最低限でもwsl上にソースを置いて開発を進めるべきですね。
containerのみで開発を進めるのが一番パフォーマンスが良いという結果になりましたが、この場合containerを作成しなおしたりすると修正途中のソースコードが消えてしまうので、開発途中でのcontainerの再作成は避けるべきです。
こういったデメリットとのトレードオフを考えてから、wslにするのかcontainerにするのかを選択すべきだと感じました。
devcontainerまだまだ使い始めたばかりなので、より良いパフォーマンス向上・開発体験向上の施策を見つけ次第ブログを更新出来たらなと思います。
コメント