Node.jsアプリ開発を進めていく過程で、だんだんとnpmパッケージが肥大化して、
node_modules のフォルダサイズがかなり大きくなってしまう経験があろうかと思います。
また開発用のDockerコンテナと、実行環境だけをまとめたコンテナの2つがある場合、それぞれに同じ内容のnpmパッケージを個別にインストールするよりも、共通のボリュームを作成して、そこに
node_modules フォルダを配置しておくだけでも、ディスクサイズの省スペース化につながります。
今回はDockerのコンテナ起動時にホスト側からリソースをマウントする際のinclude/excludeの基本を解説していきます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

Dockerコンテナで使う「Volume」をマウントするタイプの違い



Nodejsアプリ開発を行う上で、今回のお題で言うとところの
「サイズのデカいnode_modulesをどこに置くと良いか」 を考える上で、このDockerコンテナのVolumeの違いを良く理解しておくことが必要になります。
かつては単にDockerでVolumeをマウントと言うと、暗黙的に「バインドマウント」のことを指していました。
Dockerも進化を遂げており現在はDockerコンテナから扱うデータの「マウントタイプ」と呼ばれるいくつかの方式が存在しています。


Dockerでデータを扱う4つのマウントタイプ



公式のリファレンスでも詳しく記述されてあるように、Dockerのデータ管理・アクセスのメカニズムには4つの方式が選択できます。

            Volume
Bind Mounts
tmpfs Mounts
Named Pipes

        
参考|Manage data in Docker

この内、
tmpfs MountsNamed Pipes については扱いが特殊ですので、ここでは VolumeBind Mounts の2つに絞って簡単に比較したいと思います。

Dockerでの「Volume」と「Bind Mounts」の違い



元々、Dockerを使い慣れている方にはお馴染みの、ホスト側のファイルシステムをDockerコンテナ側へ共有させようとした際に使ってきた方式を「バインドマウント(Bind Mounts)」と呼んでいます。
公式の説明で利用されている模式図をそのまま引用しますと、
501x255
合同会社タコスキングダム|蛸壺の技術ブログ
出典|https://docs.docker.com/storage/

というイメージで構成されています。
まず「バインドマウント」を使うことで、ホスト上のファイル/フォルダ(つまりFilesystem)を、起動したDockerコンテナーにマウントすることでリソースをホスト間で共有できるようになります。
対して、Dockerは「ボリューム(Volume)」を別に作成することで、ホスト側のFilesystem内に「Docker area」を専有的に設けることが出来ます。
このDocker Areaでは、Dockerコンテナが独自に管理できて、かつホストなどの非Dockerプロセスからはアクセス出来ないように独立して切り離されたストレージとして使うことができます。
バインドマウントと違って、ホスト側からリソースを書き換えられることがないので、何らかのホスト側のプロセスが原因でうっかりファイルの内容が変更されたということもなくなり、高速かつ安全な処理がDocker側で可能になります。
また、この名前を付けたボリューム(
external: true フラグの設定が必要)は複数コンテナ間で同時にマウントできて、データの安全な共有もできるようになっています。
コンテナが停止した後もボリューム自体は永続化し、自動では削除されなくなり、各ボリュームの詳細は
docker volume ls 等で確認できます。


docker-composeにおけるマウントの記述法について



docker-compose.ymlのvolumesタグに関して、現在では2種類の書式があります。
まずは伝統的なワンライナーで記述する方法です。

            書式:
  [SOURCE:]TARGET[:MODE]

  + SOURCE: ホスト側であればコピー元のパス。
            もしくは定義済みボリューム名
  + TARGET: ボリュームのマウント先となるコンテナ上のパス
  + MODE: (オプション)マウント後のファイル・フォルダの属性。
          roで読み込み専用、rwで読み書き許可、など

        

docker-composeに慣れ親しんだ方だとおなじみのパターンですが、先述したマウントが増えてきたために、曖昧で紛らわしくなってきた経緯があります。
そこで、docker-composeのバージョン3.2以降で追加になったマウントタイプをより明確に書き出す記述法(通称・「長い書式」)の利用が推奨されています。

            #...
services:
  app:
    #...
    volumes:
      #👇「ボリューム」タイプでマウント
      - type: volume
        source: my-data
        target: /hoge
      #👇バインドマウント
      - type: bind
        source: ./piyo
        target: /app/piyo

volumes:
  my-data:

        

こうすることで、バインドマウントなのかボリュームなのか明瞭に理解することができます。
「長い書式」で追加になったパラメータが多少増えていますので、詳しくは公式のドキュメントを参照のこと。

参考|Compose ファイル version 3 リファレンス
合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

Dockerコンテナにマウントする際に特定のファイル・フォルダを除外する



前置きはさておき、本題のDockerコンテナからファイル・フォルダをマウント時に除外する方法をやってみましょう。
とりあえず現在のホスト側の作業フォルダ
./ の中身すべてを、Dockerコンテナ側の app へバインドマウントするいつものやつです。

            #...
services:
  app:
    #...
    volumes:
      - .:/app

        

当たり前ですが、Dockerコンテナ側から見るとすべてのリソース見えるし、ホスト側からファイル書き込みしたらDockerコンテナ側からもその変更が反映されます。


マウントからファイルを除外する



ファイルの除外は
/dev/null で置き換えることで実現できます。

            #...
services:
  app:
    #...
    volumes:
      - .:/app
      #hoge.txtは除外
      - /dev/null:/app/hoge.txt

        

これで作業フォルダにあった
hoge.txt はDockerコンテナ側から除外されます。

マウントからフォルダを除外する



ファイルだけでなく今度はフォルダごっそり除外させてみましょう。
除外のパターンは2つあり、通常割と紹介されているのが「バインドマウント」的なやり方です。

            #...
services:
  app:
    #...
    volumes:
      - .:/app
      #node_modulesは除外
      - /app/node_modules/

        

考え方としては、先にホスト側の作業ディレクトリの中身がすべてコンテナ側の
app 以下にバインドマウントされ、そして node_modules フォルダだけが名無しのボリュームへマウントされています( 「<名無しのボリューム>:/app/node_modules/」 と解釈)。
この名無しのボリュームはどこにも定義されておらず、いわば実態のない空のボリュームということで、ここにマウントされたフォルダはすべてコンテナ側からは除外されます。
このやり方ではDocker側からアクセスできるデータではなくなってしまうので、データを複数のDockerコンテナ間で共有させる必要がある場合、バインドマウントではなく、「定義済みボリューム」を作成してからそこにマウントするといいでしょう。

            #...
services:
  app:
    #...
    volumes:
      - .:/app
      - hoge:/app/node_modules/

volumes:
  hoge:

        

こちらのやり方では、
node_modules はコンテナ側のファイルストレージとは別に hoge というボリュームへ分離されて置かれます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

まとめ



今回はDockerコンテナへホスト側からファイルやフォルダをコピーする際に知っておきたい、include/excludeの指定方法について簡単に説明しました。