Dockerのキャッシュを学ぶ

Date2026/08/25 Last Modified2026/08/25

概要

Dockerの環境をAIでとりあえず作成してもらって、その通りに作業していたらスタイルがうまく反映されないという事態になった。
スーパーリロードしたら反映されるけど、また通常リロードすると戻ってしまうという現象。
原因を調べてみたところ、Dockerの構成やキャッシュなどの問題であるということはわかったが、具体的に何をどうしたら直るのかはよくわからなかった。
これは自分がちゃんと理解して、直し方を提示しないことにはきれいな形にならないな……ということで、原因を追究してみることにした。

現象とAIの調査結果をまとめる

AIが原因調査した結果  
- そもそもコンテナがホストの最新ソースを見ていなかった
- Laravelが生成した古いキャッシュもVolumeに残っていた

追加のアドバイス
- 開発環境と本番環境では構成を変えるべき
- 開発環境では「コードを外から差し替える」
- 本番環境では「コード入りの箱そのものを交換する」

うーん、これを読んでいる現時点ではさっぱりである。コンテナ、ホスト、Volumeというのがそもそも、ぼんやりとしか理解してないのもある。
ひとつずつ紐解いていこうと思う。

間違った仮説を反証してもらおう

わからないことがあるときは、自分で断定的な理論を作って誤りを指摘してもらうのがいい。

# 私の仮説
まず、コンテナとは仮想で立ち上げたOSのことであり、ホストとは仮想環境を立ち上げているサーバー、すなわちローカルPCだと思っています。 ここで、「コンテナがホストの最新ソースを見ていなかった」という現象ですが、コンテナはホストのディレクトリをマウントしているはずであり、いわば同期された関係にあるはずです。 例えばホストのAファイルが書き換わったら、コンテナが見れるのはそのリアルタイムなAファイルであるため、最新ソースを見ていなかった、という事象が起こるはずがありません。  

「コンテナはホストのディレクトリをマウントしているはず」というのは、一般的な性質ではないよ!

なるほど。Dockerは必ずディレクトリをマウントしていると思っていたけれど、バインドを明示しない限りはホストのファイルと同期していないんだ。
そうなると、それまでの設定っていったいどこにファイルを保持していたのか?
「仮想的なファイルシステムで保持」とはいうものの、その"仮想"っていうのは単なるコピーなのか、実体が無いのかで言うとどっちなのだろう。

実体のあるファイルだよ!でも、単純にエクスプローラーでパスを辿っていける場所ではなく、コンテナのファイルシステム内にあるんだ。

ファイルシステム、というのがよくわからなかったので調べたところ「ファイルやフォルダが存在する、ある保存空間・世界」と、ざっくり説明された。
つまるところ、ホスト(ローカルPC)が持っている世界とは別に作られた世界がDockerの中にあり、その中のファイルを参照しているということだと思う。
なので、Docker DesktopはこのPCに入っているから、当然このPCの世界の中にDockerがあるわけだけども、そのDockerの中身はまた別世界として扱う……というマトリョーシカみたいな構造なんだと思う……多分。

それでいうと、ちょっと合点がいく部分があって、以前Dockerについて調べたとき「Docker Engineさえ入れれば、アプリケーション環境が作れる」と知った。
これはつまり、bindする方式だったら/var/www/にアプリが無いといけないから、そこで矛盾する。
なので、Dockerの中にファイルシステムが構築される(/var/wwwの部分も含めて)というのは、ある程度納得ができるともいえる。

VolumeってMySQLみたいなものだと思ってたけど違うの?

次はVolumeという概念がよくわからない。データを格納することはわかるけれど、具体的に何のデータなのだろうか。
“Volumeに古いキャッシュが残っていた"というのは、いったいどういう現象なのだろう。今回はビルドキャッシュだし、ファイルのはずなのに。

VolumeはDockerコンテナのデータを永続化するための保存場所

永続化、というとピンとこなかったけれど、紐解いてみたらはっきりわかった。
compose.yamlの中でこんな記述がある。

    volumes:
      - .:/var/www/html
      - node-modules:/var/www/html/node_modules

これはまず、.:/var/www/htmlによってソースコード全体をホストからコンテナへbind mountしている。
つまり、これだけなら全てのソースコードがホストをマウントしていることになる。
けれど、そうすると不都合なこともある。

例えば、node_modulesというのはnpm installする環境によって差異が出るものである。
ホストではv20だけどコンテナはv24とか。
Docker内と環境が違うとなると、そのままバインドしたものを持ってくるとバージョンが合わなくなったりする。

そこで登場するのがVolumeである。
Volumeにコンテナ専用のソースコードを予め用意しておくことで、マウントしたときにかぶせるような形でnode_modulesのファイルを置き換えてしまえばよいというわけだ。
コンテナは再構成するたびにキャッシュ等は失われてしまう。なのでVolumeで永続化をすることによって解決する。

ということで「Volumeに古いキャッシュが入っていたのでスタイルが反映されなかった」という問題を振り返ってみると、

Vite
├─ public/build       ← npm run build の成果物
├─ public/hot         ← dev server関連

このようなビルド成果物が入る場所を永続化の対象とした上で更新していなかった場合、いくらホスト側でビルドファイルをマウントさせたところで、Volumeによって上から被せて古い状態に戻ってしまう。
だから正確には「本来は永続化すべきでないビルド成果物をVolumeに置いてしまい、それをマウントしていたことによって新しい成果物か隠されてしまった」ということになる。

マウントってどのタイミング?

この問題を整理して行くうえで一つ疑問がさらに浮上。
「コンテナのファイルってどの段階で生成されるんだろう?」
わざわざbind mountで消えてしまうファイルも置くんだろうか。マウントされたものは消えてしまうんだろうか。

Dockerが構築されるまでには2段階ある

Dockerを作る段階は「Dockerイメージを作る段階」と「コンテナを起動する段階」がある。
流れとしてはこのようになる。

① Docker imageをビルドする
② imageからcontainerを構成する
③ bind mount / Volume / tmpfsをマウントする
④ entrypoint / commandを実行する
⑤ LaravelやViteが動く

ここで重要なのが、imageをビルドする段階で完成済みのファイルシステムができるということ。
つまり、このimageというものの中にPHPなどのファイルが入っているということになる。
これを起動するのがcompose upコマンドであり、ここでマウントの指定があれば上から被せる形で取り込む。

そして、これらのファイルの準備が終わってからようやくnpm ciなどのコマンドを実行する。
これでようやく動く形になる。

mount前提ならソースを生成しない可能性もあるよ

これはDockerfileの定義によるもので、仮にCOPYをしているのであればマウントに拘わらずImageにも存在することになる。
ホストのソースを信頼することによって、Imageを軽くしていると言える。

しかし、本番環境ではマウント前提であってもImageに焼き込むことが多いという。

本番とローカルで環境を変える理由とは

ここまで来ると、当初の疑問であった「ローカルと本番でcompose.yamlが違うのはなぜ」という点もほとんど解消したようなもの。

いうなれば、力関係が逆転しているということになるだろうか。
ローカルではローカル用の実行環境とDocker用の実行環境が2つ存在しており、主にローカル用の実行環境に依存して開発など行うからして、ローカルのホストをマウントして色々と構成していくほうが多い。
しかし本番に近づくにつれて、永続化やキャッシュ保持など、あらゆるものがDocker一つに集約されていきホストを参照することが減ってくる。
このグラデーションが本番とローカルの差になってくるのだと思う。

ひとまずここまででDockerの学びをひと段落させて、実際に手を動かしてみたくなってきた。