プロジェクトの Dockernize と肥えた node_modules
Posted: Updated:
Dockernize
node アプリ + HTML/CSS/JavaScript のリポジトリを Dockernize したときの話。最近 Docker の機運が高まっているのはたまたまです。
同じプロジェクトのバックエンドのリポジトリ(ただし別言語)が Dockerfile 内で依存解決をしていたので、おもむろに RUN cd /src && npm i && npm run build
的な処理を記述したら時間がかかりすぎて爆死しました。
肥えた node_modules
予想はしてましたが node_modules
以下の依存ツリーが肥大化しているのが原因です。
$ du -k -d 1 node_modules | sort -nr
466792 node_modules
85224 node_modules/sc5-styleguide
60400 node_modules/gulp-svg-sprite
32844 node_modules/browser-sync
27344 node_modules/karma
27264 node_modules/gulp-cssnext
24996 node_modules/babel-core
22884 node_modules/gulp-eslint
21224 node_modules/eslint
20812 node_modules/karma-browserify
16900 node_modules/browserify
...
..
.
まだフロントエンド系のビルド環境しか構築していないのですが、それでも 450MB オーバーのサイズ( npm dedupe
前 )を誇っています。単純に物量が多いのもありますが、フロント系の便利ツールは phantomjs や websocket (ws) を含んでいることが多く余計に時間がかかりやすいです。
devDependencies のインストールを避ける
今回 Docker イメージのビルドは Circle CI で行っています。
Circle CI の node_modules
はキャッシュが効いているので、JavaScript や CSS などのビルドは Circle CI 上で行って、イメージ内では npm i —production
することにしました。devDependencies
さえ避けられれば大分軽くなります。あたりまえ体操ですね。
circle.yml
予め npm run build
でビルド済みのファイルを生成して、Docker イメージへの Context のアップロードに備えます。ここでビルドは済ませているのでイメージ内では devDependencies
を必要としなくなります。
deployment:
docker:
branch: master
commands:
- npm run build
- docker build -t $CIRCLE_PROJECT_REPONAME:latest ./
.dockerignore
.dockerignore
( doc ) にパスを指定すると、Context としてイメージにアップロードされなくなるので、ADD
や COPY
の対象から外れます。node_modules
ほか Docker イメージに送り込みたくないものを書きます。
.git
node_modules
src
typings
...
..
.
Dockerfile
で、Docker イメージ内では —production
だけインストールします。今の所 dependencies
を必要とする node アプリが本格的に開発される前なのですぐ終わります。
FROM node:0.12.4 # copy sources COPY . /project # npm install RUN cd /project && \ npm install --production # timezone RUN echo "Asia/Tokyo" > /etc/timezone && \ dpkg-reconfigure -f noninteractive tzdata EXPOSE 80 CMD ["node", "/project/index.js"]
つまり、使うモジュールにもよりますが node アプリが育った時点でまた時間がかかるということです/(^o^)\
予防策を考える
node アプリ側が成長したときに備えてビルド時間が長大化する前の予防策を考え中ですが、Dockerfile の命令が 1 行実行されるたびコミットされるため、イメージ内ではあまり小賢しい差分更新ができない印象...。
没案
書きながら
- package.json を元に
devDependencies
を避ける.dockerignore
を動的に生成する - Context をアップロードする前に、node_modules から
devDependencies
を消す
などでも良いような気がしましたが、node-gyp
が走るようなモジュールが含まれてたらダメ(ネイティブモジュールとかダメよね?)なので没です。
未検証案
あとはこんなところでしょうか。
npm dedupe && npm shrinkwrap
でshrinkwrap.json
を生成して物量を減らす- Circle CI ( ホスト ) のディレクトリをイメージ内の
node_modules
にマウントしてキャッシュを効かせる
マウントして Circle CI 側のキャッシュを効かせるのはうまくいくかもしれませんね。dedupe と shrinkwrap の駆使はメンバーによっては、明解なワークフローが思いつけばやりたい。
どのみち静的なファイルと node アプリのリポジトリは、分離しないといけないような気がしている今日この頃でした。