はじめに
Dockerのハンズオンを進めていたときに、docker compose up でエラーが発生しました。
最初は node_modules を削除して入れ直したりしましたが解決せず、原因の特定にかなり苦戦しました。
同じところで詰まる方がいるかもしれないので、原因と解決方法をまとめます。
今回参考にしていたハンズオンはこちらです。
発生したエラー
docker-compose.ymlに volume を追加し、ターミナルでdocker compose upを実行したところ、以下のエラーが発生しました。
api-1 | Error [TransformError]:
api-1 | You installed esbuild for another platform than the one you're currently using.
api-1 | This won't work because esbuild is written with native code and needs to
api-1 | install a platform-specific binary executable.
api-1 |
api-1 | Specifically the "@esbuild/darwin-x64" package is present but this platform
api-1 | needs the "@esbuild/linux-arm64" package instead. People often get into this
api-1 | situation by installing esbuild on Windows or macOS and copying "node_modules"
api-1 | into a Docker image that runs Linux, or by copying "node_modules" between
api-1 | Windows and WSL environments.
api-1 |
api-1 | If you are installing with npm, you can try not copying the "node_modules"
api-1 | directory when you copy the files over, and running "npm ci" or "npm install"
api-1 | on the destination platform after the copy. Or you could consider using yarn
api-1 | instead of npm which has built-in support for installing a package on multiple
api-1 | platforms simultaneously.
api-1 |
api-1 | If you are installing with yarn, you can try listing both this platform and the
api-1 | other platform in your ".yarnrc.yml" file using the "supportedArchitectures"
api-1 | feature: https://yarnpkg.com/configuration/yarnrc/#supportedArchitectures
api-1 | Keep in mind that this means multiple copies of esbuild will be present.
api-1 |
api-1 | Another alternative is to use the "esbuild-wasm" package instead, which works
api-1 | the same way on all platforms. But it comes with a heavy performance cost and
api-1 | can sometimes be 10x slower than the "esbuild" package, so you may also not
api-1 | want to do that.
api-1 |
api-1 | at generateBinPath (/app/node_modules/esbuild/lib/main.js:2029:17)
api-1 | at esbuildCommandAndArgs (/app/node_modules/esbuild/lib/main.js:2110:33)
api-1 | at ensureServiceIsRunning (/app/node_modules/esbuild/lib/main.js:2267:25)
api-1 | at transform (/app/node_modules/esbuild/lib/main.js:2168:37)
api-1 | at file:///app/node_modules/tsx/dist/index-7AaEi15b.mjs:14:2865
api-1 | at applyTransformers (file:///app/node_modules/tsx/dist/index-7AaEi15b.mjs:14:1266)
api-1 | at transform (file:///app/node_modules/tsx/dist/index-7AaEi15b.mjs:14:2812)
api-1 | at load (file:///app/node_modules/tsx/dist/esm/index.mjs?1777034658465:2:2348)
api-1 | at async nextLoad (node:internal/modules/esm/hooks:864:22)
api-1 | at async Hooks.load (node:internal/modules/esm/hooks:447:20)
api-1 |
api-1 | Node.js v18.20.8
エラーメッセージを読むと、次のことが分かりました。
- 今、使っている環境と違う OS 用の
esbuildが入っている -
@esbuild/darwin-x64は Mac 用
でも Docker コンテナの中では@esbuild/linux-arm64が必要
修正前ファイル
問題が起きたときのdocker-compose.ymlは以下のようになっていました。
version: '3.8'
services:
client:
build: .
ports:
- "5173:5173"
depends_on:
- api
volumes:
- .:/app
- /app/node_modules # コンテナ側の node_modules をホスト側とは別に保持するための設定
api:
build: ./todo-api
container_name: api
ports:
- "3000:3000"
volumes:
- ./todo-api:/app
- /app/node_modules # コンテナ側の node_modules をホスト側とは別に保持するための設定
エラーの原因
原因は、Macで作られた node_modules をDockerコンテナの中で参照してしまっていたことです。
何が起きていたのか
Dockerコンテナの中はLinux です。
一方、手元のMacでnpm installすると、Mac 用の依存関係がnode_modules にインストールされます。
今回の設定では、以下のようにディレクトリ全体をマウントしていました。
- .:/app
- ./todo-api:/app
この書き方をすると、
Docker コンテナの /app フォルダの内容が、
ホスト(Mac)のファイルで上書きされて見える状態になります。
つまり、
コンテナの中で本来使うべき Linux 用の node_modules ではなく、Mac 側の node_modules が見えてしまう状態になっていました。
なぜこのエラーが発生したのか
esbuild のような一部のライブラリは、OS ごとに専用のファイルを持っています。
例えば今回の環境では、以下のようになります。
- Mac で
npm install
→@esbuild/darwin-x64がインストールされる(Mac 用) - Docker コンテナの中は Linux
→@esbuild/linux-arm64が必要(Linux 用)
しかし、Docker の設定によって、
Mac用の node_modules を参照してしまったため、
Linux なのに Mac 用の部品を使おうとしてしまい、
起動時にエラーが発生しました。
つまり一言でいうと、
Mac用の部品を、LinuxのDockerコンテナの中で使おうとしていたことが原因です。
解決法
解決方法は、ホスト側の node_modules をコンテナに見せないこと です。
そのために、/app 全体をマウントするのをやめて、開発中に変更を反映したいファイルだけを個別にマウントするように変更しました。
修正後ファイル
version: '3.8'
services:
client:
build: .
ports:
- "5173:5173"
depends_on:
- api
volumes:
- ./src:/app/src
- ./public:/app/public
- ./index.html:/app/index.html
- ./vite.config.ts:/app/vite.config.ts
- ./tsconfig.json:/app/tsconfig.json
- ./tsconfig.app.json:/app/tsconfig.app.json
- ./tsconfig.node.json:/app/tsconfig.node.json
api:
build: ./todo-api
ports:
- "3000:3000"
volumes:
- ./todo-api/src:/app/src
- ./todo-api/tsconfig.json:/app/tsconfig.json
なぜこの修正で解決したのか
修正前は、/app ディレクトリ全体をマウントしていたため、
ホスト側の node_modules がコンテナから参照される状態になっていました。
一方、修正後は必要なファイルだけを個別にマウントするように変更したことで、
node_modules はコンテナ内部にあるものがそのまま利用される状態になりました。
その結果、Linux 用の node_modules が正しく利用されるようになり、
OS の違いによる不整合が解消されたため、エラーが発生しなくなりました。
これにより、以下の状態になります。
- 開発中に編集するソースコードや設定ファイルはコンテナに反映される
- node_modules はホスト側から持ち込まれない
- コンテナの OS(Linux)に対応した依存関係が正しく使用される
補足
今回の直接の原因は docker-compose.yml の volume mount 設定でした。
ただし、Dockerfile 側でも .dockerignore がないと、ホスト側の node_modules をイメージにコピーしてしまう可能性があります。
今回使用していた Dockerfile は以下のようになっていました。
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "run", "dev"]
COPY . . は、ホスト側のファイルをコンテナにコピーする命令です。
もし .dockerignore が設定されていない場合、
ホスト側の node_modules も Docker イメージにコピーされてしまう可能性があります。
その状態で npm install を実行すると、依存関係の不整合につながることがあります。
そのため、以下のように .dockerignore を設定しておくことが重要です。
node_modules
これにより、Docker イメージをビルドするときに
ホスト側の node_modules がコピーされるのを防ぐことができます。
終わりに
今回のエラーを通して、以下のことを学びました。
- Docker コンテナの中と自分のパソコン(ホスト)は別の環境であること
- node_modules は OS に依存する場合があること
- volume でフォルダを共有すると、ホスト側のファイルがそのまま使用されることがあること
見た目は同じプロジェクトでも、
- ホスト側:Mac
- コンテナ側:Linux
という違いがあります。
そのため、node_modules のように
OS に依存するものをそのまま共有すると、今回のようなエラーが発生する可能性があると理解しました。
今回の経験を通して、
Docker の仕組みを正しく理解することの重要性を実感しました。
今後もトラブルが発生した際には、
「ホストとコンテナは別環境である」という前提を意識しながら、
原因を一つずつ切り分けて対応していきたいと思います。
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼