🆚

ボリューム設定優先度バトル, .devcontainer.json vs docker-compose.yml vs Dockerfile

に公開

2025年話題になった 『npmサプライチェーン』から数ヶ月。


私たち開発者のコンテナボリューム設定は、devcontainer.json・docker-compose.yml・Dockerfileの3つに分かれ、混沌を極めていた……

(日本の全ての/appが devcontainer.json / docker-compose.yml / Dockerfileに分かれている図)



すみません, 仮面ライダービルド未履修です. タイトルをつけた思いつきの勢いです.

npmサプライチェーンといえば2025年の話題の1つとなりました. post-installの無効化やnpm installするパッケージは直近数日間のものを除くといった様々な方法が考慮されtましたが, その1つがDev Containerでした.

Dev Containerの説明については詳しい記事や解説もしくは任意の生成AIに委ねますが, ざっくり説明するとDockerのコンテナシステムを利用して, ローカルに本番環境とは別に開発用のコンテナを立ててその中で開発することで, ホスト側PCへの汚染を避けようという目的のものです. それ以外にも環境差分を減らせたり開発環境のポータブル性を上げれたりといくつか利点があります. もちろんコンテナ環境ということもあり制約やわかりづらい点もあります.

Dev Containerでローカルのボリューム設定どうする?

そのわかりづらさの1つがDocker Compose含めたコンテナ環境特有のボリューム設定の問題です. Dev Containerを使う場合, HMR等の関係でマウントした先のファイルが更新されたら即座に反映されて欲しいしそれがホスト側にも反映されていて欲しいです.

この要求自体はシンプルですが, 現実はもう少し複雑です.
まずファイルをコンテナの中にからアクセスするために, COPY / volume 2つのやり方が存在します.

DockerfileのCOPYとvolumeオプション

COPYはシンプルですね. DockerfileのCOPY命令です.
ローカルファイルをそのままコピーして, コンテナ内の特定パスに複製します. ホスト側の変更は伝わりませんし, コンテナで加えた変更ももちろんホストに伝達されません.

volumeオプションの場合はbind mountのため, ホスト側のfsに対して操作する形になります. ので, ホスト側と相互に同期できるわけですね.
ちなみにdocker-compose.ymlのvolumeオプションとdockerのvolumeオプションは同じものを指しています.

つまるところ本番環境のDockerfileではCOPYしたいが, 開発環境だとvolumeマウントの方が使い勝手が良さそうです.

ここに来て設定優先度バトル 1st round: Dockerfile' COPY vs docker-compose.yml' volume

この場合, docker-compose.ymlのvolumeが勝ちます. COPYとvolumeで同じファイルやディレクトリを指していた場合, COPYで指定してもvolume側の設定が勝ちます. つまり, volumeで上書き設定すればコンテナ側と強制的に同期できるわけです.

If you bind mount file or directory into a directory in the container in which files or directories exist, the pre-existing files are obscured by the mount

コンテナ内の既存のファイルやディレクトリがある場所に、ファイルやディレクトリをバインドマウントすると、マウントした内容によって既存のファイルは隠されます。

https://docs.docker.com/engine/storage/bind-mounts/

ここでくる 2nd round devcontainer.jsonのdockerComposeFile

さてここまで, docker composeとdockerの話をしてきましたが最後にdev containerの話をします. Dev Containerではボリュームマウントの設定がないように見えますが, 実はdevcontainer.jsonのdockerComposeFileがここに関連してきます.

dockerComposeFileでは, 以下のように複数のcomposeファイルを受け付けられます.

devcontainer.json
{
  "dockerComposeFile": ["../docker-copmose.yml", "docker-copmose.yml"]
}

複数ファイルを記述したとき, 一番最後に書かれたdocker-compose.ymlの設定で今までのdocker-compose.ymlの設定を上書きします. つまりvolume設定を本番環境と開発環境で変えることができるわけです. ただし, それは基本的により大きな範囲でmountするようなことにしか使えないのですが...

結論

ということで, まとめると以下のような設定でうまく書けそうです.

  • 開発用のdocker-compose.ymlと本番環境の`docker-compose.ymlは分ける
  • 本番用ののdocker-compose.ymlは極力volumeを使わずにCOPYコマンドで内部ファイルを複製する.
  • 開発環境のdocker-compose.ymlはvolume設定で適度に広めに設定する.(広すぎるとパフォーマンスに影響する)

一件落着混沌を見せていたボリューム設定バトルはこれにて終焉を迎えました. めでたし, めでたし.

Discussion