最近のプログラミング言語はたいていパッケージマネージャーがある。
よくある実装だと、vendor/
や node_modules/
といったディレクトリに必要なライブラリが全部入る。
しかし、この方式はDockerで開発する際に問題がある。 この記事では、実例のひとつして Node.js とそのパッケージマネージャ npm で解説する。
volume mount によって空になる問題
- 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 で出る補完と、コンテナで実行するときで差異がでそう。実際に遭遇したことはないけれど。