Docker Composeウォッチでファイルを同期する
Dockerを使用してポータブルで簡単に再現可能な開発環境を作成する際、コードベースに対する変更を、毎回再ビルドする必要なく、コンテナ内ですぐに有効にする方法を見つける必要があります。可能な解決策は、ホストのディレクトリをコンテナ内に直接マウントすることですが、これにはコンテナの隔離とポータビリティを破る必要があり、ホストのディレクトリ構造に依存することになります。この問題を解決するために、docker-compose watchを使用することができます。
このチュートリアルでは、docker-compose watchを使用してホストファイルをコンテナ内で同期させる方法を学びます。
このチュートリアルでは、あなたは学びます:
- ボリュームとバインドマウントの違いは何ですか?
- ホストシステムとコンテナ間でファイルを同期させるために、docker-compose watchを使用する方法
| カテゴリー | 使用された要件、規約、またはソフトウェアのバージョン |
|---|---|
| システム | 配分非依存 |
| ソフトウェア | ドッカーコンポーズ |
| 他の | Dockerとdocker-composeに慣れていること |
| 協定 | # – ルートユーザーとして直接、または sudo コマンドを使用して実行される必要があるLinuxコマンド$ – 権限のない通常のユーザーとして実行される必要があるLinuxコマンド |
Dockerボリュームとバインドマウント
コンテナ内のデータは永続的ではありません:これは、コンテナが破壊されると、その内部のすべてのデータが失われることを意味します。Dockerを使用してデータの永続性を実現するためには、基本的に二つの方法があります:ボリュームを使用するか、バインドマウントを使用するかのいずれかです。各ソリューションにはそれぞれの利点と欠点があります:これらを要約しましょう。
Dockerボリューム
匿名ボリュームと名前付きボリュームを区別することができます。彼らの動作はほぼ同じですが、今後見る1つのケースを除いてはそうです。
匿名またはランダムに名付けられたボリュームは、通常、DockerfileでVOLUME命令が使用される場合や、docker volume createコマンドがボリューム名を引数として指定せずに呼び出される場合に作成されます。対して、名前付きボリュームは、その名が示す通り、明示的に名前を割り当てるボリュームです。
以下の例では、公式の「httpd」イメージに基づいてコンテナを実行し、名前付きボリューム(「httpd_data」と呼びます)を作成することを指定して、コンテナディレクトリの/usr/local/apache2/htdocs/にデータを永続化します:
docker run -v httpd_data:/usr/local/apache2/htdocs httpdコンテナが起動されるとき、ボリュームが空であれば、コンテナのターゲットディレクトリ(この場合、/usr/local/apache2/htdocs)に存在するデータがボリューム内にコピーされます。ボリュームが空でない場合は、代わりにコンテナ内のデータがターゲットディレクトリの内容を隠してしまいます。これは、mnt コマンドで既存のディレクトリ内にファイルシステムをマウントするのと同じです。
通常、コンテナが削除されると、それによって使用されているボリュームは保持されます。ただし、docker run コマンドを --rm オプションで実行してコンテナが作成される場合は例外であり、その場合、コンテナが存在していると自動的に削除されます。この特別なケースでは、名前付きボリュームとは異なり、文脈上で作成された匿名ボリュームはコンテナとともに自動的に削除されます。
ボリュームは、Dockerを使用してアプリケーションを配布する際にデータ永続性を達成するための一般的な推奨ソリューションです。なぜなら、それらはDocker自身によって作成および管理されるからです。しかし、開発中には理想的なソリューションではありません。コードベースに対する変更をコンテナ内に反映させるには、後者を再構築する必要があるからです。
バインドマウント
バインドマウントはしばしば勧められませんが、上記の問題に対する可能な解決策を示しています。バインドマウントを使用することで、ホストディレクトリをコンテナ内で直接アクセス可能にすることができます。以下の例では、ホストのsrcディレクトリを、httpdコンテナ内の/usr/local/apache2/htdocsディレクトリにバインドマウントします。
sudo docker run -v $(pwd)/src:/usr/local/apache2/htdocs httpdこの戦略はコンテナ内でコード変更を即座に反映させる方法を提供するという利点がありましたが、2つの主な欠点があります:
- それはコンテナの隔離性とポータビリティを破壊します(コンテナがホストのディレクトリ構造に依存するようになります)。
- 特にSELinuxのような追加のセキュリティ対策がホストシステムで使用される場合、権限の管理がより難しくなります。
ここに典型的な例があります:ホストシステム上に、私たちのユーザーとそのユーザーグループが所有し、他のユーザーによって書き込み不可のソースファイルがsrcディレクトリに保存されているとします。このディレクトリをコンテナ内にバインドマウントする場合、対象のサービスがホストマシン上の私たちのユーザーと同じUIDで実行されることを確認しなければなりません。そうでないと、そのディレクトリ内でファイルを作成したり削除したりすることができません。もしそのサービスがrootとして実行されている場合、ディレクトリ内にファイルを作成できますが、ホストマシン上の特権のないユーザーでそれらを変更することはできません。rootless dockerを使用する場合、UIDがコンテナ内でシフトされるため、さらに混乱する可能性があります。
docker-composeを使用して監視する
Docker-composeのウォッチはDockerエンジンの機能ではないので、厳密にはデータ永続性を実現したりホストシステムとコンテナの間でデータを共有したりする新しい方法ではありません:これは、マルチコンテナアプリケーションを簡単に実行するためのツールであるdocker-composeのバージョン2.22から利用可能な機能です。
docker-compose watchを使用することにより、コンテナの分離を維持しつつ、コードベースに加えた変更が即座にコンテナ内に反映されるのを確認できます。これは、ホスト上の指定されたパスを監視して変更を検出することによって実現されます。例えば、ファイルに変更が見つかると、そのファイルはコンテナ内で自動的かつ透過的に同期されます。
例を見てみましょう。ホストのsrcディレクトリ内に「index.html」ファイルがあるとします。このファイルの内容は以下の通りです:
<h1>Hello world!</h1>srcディレクトリの内容を、コンテナが作成されたときにターゲットディレクトリにコピーするには、実行したいサービスの元のDockerfile(この場合はhttpd)を拡張する必要があります。
FROM httpd:latest
COPY src/ /usr/local/apache2/htdocsさて、同じディレクトリに、コンテナを構築するための指示と、srcディレクトリの内容の変更を監視するためのdocker-composeファイルを作成しましょう:
services:
httpd:
build:
dockerfile: Dockerfile
ports:
- 8080:80
develop:
watch:
- action: sync
path: ./src
target: /usr/local/apache2/htdocs私たちは、docker-composeファイル内のwatch属性を介して「監視」指示を提供します。action(この場合はsync)、変更を監視するdocker-composeファイルに対する相対的なpath(./src)、および変更がコンテナ内にどのように反映されるかを制御するtargetを指定します。さらに、ignoreフィールドを使用して、特定のファイルを監視から除外することもできます:除外パターンはpathに対して相対的と見なされます。例えば、./src/node-modulesディレクトリを除外したい場合、次のように書きます。
services:
httpd:
build:
dockerfile: Dockerfile
ports:
- 127.0.0.1:8080:80
develop:
watch:
- action: sync
path: ./src
target: /usr/local/apache2/htdocs
ignore:
- node_modules/さて、docker-compose upコマンドを実行できます。ファイルモニタリングを有効にするには、--watchオプションを使用します:
docker-compose up --watchカスタムイメージが作成され、コンテナが起動されると、ウォッチが確立されたことを確認するためにログを見てみましょう:
STEP 1/3: FROM httpd:latest
STEP 2/3: COPY src/ /usr/local/apache2/htdocs
--> a03c7ea1c705
STEP 3/3: LABEL "com.docker.compose.image.builder"="classic"
COMMIT docker.io/library/test-httpd
--> 01c475f85dc8
Successfully tagged docker.io/library/test-httpd:latest
01c475f85dc840ceb98e9257142257c27037fe8c4df31db423c90bf6430e0eb6
Successfully built 01c475f85dc8
Successfully tagged test-httpd
[+] Running 2/1
✔ Network test_default Created 0.0s
✔ Container test-httpd-1 Created 0.1s
⦿ Watch enabled
Attaching to httpd-1
httpd-1 | AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.89.4.2. Set the 'ServerName' directive globally to suppress this message
httpd-1 | AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.89.4.2. Set the 'ServerName' directive globally to suppress this message
httpd-1 | [Fri Feb 28 14:15:07.472766 2025] [mpm_event:notice] [pid 1:tid 1] AH00489: Apache/2.4.63 (Unix) configured -- resuming normal operations
httpd-1 | [Fri Feb 28 14:15:07.473037 2025] [core:notice] [pid 1:tid 1] AH00094: Command line: 'httpd -D FOREGROUND'ホストのポート8080をコンテナ内のポート80にバインドしたので、http://localhost:8080に移動することで、ソースディレクトリ内のindex.htmlファイルの内容を視覚化できるはずです:
さて、index.htmlファイルの内容を次のように変更しましょう:
<h1>Hello linuxconfig.org!</h1>
変更を保存するとすぐに、ログを読み取ることでファイルが同期されたことを確認できます:
⦿ Syncing service "httpd" after 1 changes were detected実際に、http://localhost:8080に移動すると、コンテナ内に変更が反映されているはずです:
コンテナ内のUSERがtargetディレクトリに書き込むことができる必要があるため、docker-compose watchが機能するためにはそれが自明である。
docker-composeファイルの内容から推測できるように、「sync」は利用可能なウォッチアクションの唯一のものではありません。他のアクションはsync+restartとrebuildです。前者が使用されると、ホスト上の指定されたファイルの変更がコンテナ内のターゲットディレクトリに同期され、さらにサービスコンテナが再起動されます。これは、サービスを再起動する必要がある設定ファイルの同期に便利です。一方、rebuildアクションが使用されると、指定されたファイルの変更がイメージの再構築と実行中のサービスコンテナの置き換えをトリガーします。
結論
このチュートリアルでは、docker-compose watchを使用して、ホストファイルをコンテナ内で自動的に同期させる方法を学びました。docker-compose watchはすべての状況でバインドマウントの代わりになることを意図しているわけではありませんが、開発中に理想的なソリューションです。なぜなら、コンテナの隔離性やポータビリティを損なうことなく、コードベースに加えた変更を即座にコンテナ内で確認できるからです。


