node_modules/ and Docker volume mount 問題と対策

最近のプログラミング言語はたいていパッケージマネージャーがある。 よくある実装だと、vendor/node_modules/ といったディレクトリに必要なライブラリが全部入る。

しかし、この方式はDockerで開発する際に問題がある。 この記事では、実例のひとつして Node.js とそのパッケージマネージャ npm で解説する。

volume mount によって空になる問題

node.js - Docker-compose: node_modules not present in a volume after npm install succeeds - Stack Overflow

  • docker image ビルド時に npm install する
  • image の中に node_modules/ が作られてライブラリもインストールされる

ここまでが docker build。そして次は docker run でコンテナを実行する。

  • docker run により volume mount つきでコンテナを起動する
  • volume mount によりディレクトリが同期される
  • コンテナの外側(ホスト側)では npm install してないので、空の node_modules/ がコンテナ内に同期されてしまう
  • node_modules/ が空なので、もちろんアプリは動作しない

回避策: data volume をつかう

Stack Overflow のとある回答によると、data volume(ホスト側のディレクトリを指定しない volume mount)は優先して認識されるようで、node_modules/ の中身を data volume にしてしまえばOK。ということらしい。

その回答では dokcer-compose.yml の記述例がある。

    volumes:
        - .:/app
        - /app/node_modules

確かに1行追加するだけで解決できるのでスマートではあるのだが、これは data volume の仕様を知らないとわかりづらい。 個人的には「えぇ〜、もっと良い方法ないのかな・・」と思った。

コンテナの外側(ホスト側)でも npm install するべき?

上に書いた回避策で、たしかにコンテナ内部の node_modules/ が消えることは回避できた。

しかし、コンテナの外側の node_modules/ は空のまま。それだと困る場合がある。 IDE などのツールでコードを書く際にライブラリの補完が出ない。解決するためにコンテナの外側でも npm install するとよい。

せっかく実行環境をコンテナに閉じ込めたのに、外側でnpmを実行するのはイケてない。 実はこれも回避策があって、コンテナの中で npm install して、取ってきたファイルをコンテナの外に持ってこればよい

Docker を使いつつ、コンテナの外側に node_modules/ を作る

docker run と volume mount を使って、npm install をコンテナ内でやって、volume mount の力でインストールした中身を外側に持ってくる方法がある。

つまりこういうこと!便利!

docker run --rm -v $PWD:/app -w /app node:10-alpine npm install

しかし -v とか -w とかコマンドが長くなり覚えづらい。個人的には「えぇ〜もっとスマートな方法ないのかなぁ・・」と思う。

まぁそこは Makefile などでコマンド化したらいいのかな。

Native Extension があった場合おかしくなるかも・・

Docker のコンテナイメージが Linux ベースだが、ホスト側は Mac OS だとどうなるか。Node.js であれば大抵のライブラリはどちらでも動くはずだけど、native extension が混ざっていた場合はコンテナ内で実行することで Linux 用のバイナリが生成されてしまって、Mac で出る補完と、コンテナで実行するときで差異がでそう。実際に遭遇したことはないけれど。