バインドマウントの利用
4 部 においては、データベース内のデータを維持するためにボリュームマウントを利用しました。 ボリュームマウントというものは、アプリケーションデータをどこかに保持しなければならないときに採用する優れた選択肢の一つです。
バインドマウントというのは、それとは別のマウントです。 これによってホスト上のファイルシステムをコンテナーとの間で共有することができます。 アプリケーションを使った作業をする際には、バインドマウントを使ってソースコードをマウントすることで、それをコンテナー内で利用することができます。 ソースコードのファイルを保存した場合には、コンテナー内でそのコードの変更を即座に確認することができます。 コンテナー内ではファイルシステムへの変更を監視し、それに対して応答するようなプロセスが起動できることを意味します。
本章ではバインドマウントの利用方法について見ていきます。 またファイル変更の監視を行う nodemon というツールを使って、アプリケーションの再起動を自動的に行います。 こういったものは、たいていの言語フレームワークにも同様に含まれています。
ボリュームタイプの単純な比較
以下では --mount
を使って実現する名前つきボリュームとバインドマウントの例を示しています。
- 名前つきボリューム (named volume):
type=volume,src=my-volume,target=/usr/local/data
- バインドマウント:
type=bind,src=/path/to/data,target=/usr/local/data
以下の表は、ボリュームマウントとバインドマウントの主な違いについて示します。
名前つきボリューム | バインドマウント | |
---|---|---|
ホスト上の場所 | Docker が決めます | ユーザーが決めます |
コンテナー内容の別のボリュームによる公開 | 可 | 不可 |
ボリュームドライバーのサポート | 有り | 無し |
バインドマウントを試す
アプリケーションの開発に向けてバインドマウントをどのように活用していけば良いのか。 そういったことを学ぶ前に、まずは単純な実験を通じて、バインドマウントがどのように動作するのかを実践で理解していくことにしましょう。
ここで確認しておくことですが、
getting-started-app
ディレクトリは Docker Desktop のファイル共有設定において定義されたディレクトリ内にあることが必要です。 この設定は、コンテナーとの間で共有するのがファイルシステムのどこであるかを定義します。 その設定へのアクセス方法の詳細については ファイル共有 を参照してください。端末を開いて
getting-started-app
ディレクトリに移動します。以下のコマンドを実行して
bash
を起動します。 これはubuntu
コンテナーでありバインドマウントを利用しています。$ docker run -it --mount type=bind,src="$(pwd)",target=/src ubuntu bash
$ docker run -it --mount "type=bind,src=%cd%,target=/src" ubuntu bash
$ docker run -it --mount type=bind,src="/$(pwd)",target=/src ubuntu bash
$ docker run -it --mount "type=bind,src=$($pwd),target=/src" ubuntu bash
オプション
--mount type=bind
はバインドマウントの生成を指示するものです。 ここでsrc
はホストマシン上のカレントなワーキングディレクトリ (getting-started-app
) であり、target
はコンテナー内に現れることになるディレクトリ (/src
) のことです。コマンド実行後 Docker は、コンテナーのファイルシステムにおけるルートディレクトリにおいて
bash
の対話セッションを起動します。root@ac1237fad8db:/# pwd / root@ac1237fad8db:/# ls bin dev home media opt root sbin srv tmp var boot etc lib mnt proc run src sys usr
src
ディレクトリに移動します。このディレクトリは、コンテナーが起動した際にマウント先としたディレクトリです。 このディレクトリ内の内容一覧を確認すれば、ホストマシン上の
getting-started-app
ディレクトリ内にあるファイルとまったく同じであることがわかります。root@ac1237fad8db:/# cd src root@ac1237fad8db:/src# ls Dockerfile node_modules package.json spec src yarn.lock
myfile.txt
というファイルを新たに生成します。root@ac1237fad8db:/src# touch myfile.txt root@ac1237fad8db:/src# ls Dockerfile myfile.txt node_modules package.json spec src yarn.lock
ホスト上において
getting-started-app
ディレクトリを開きます。 確かにmyfile.txt
がそのディレクトリ内に存在しています。├── getting-started-app/ │ ├── Dockerfile │ ├── myfile.txt │ ├── node_modules/ │ ├── package.json │ ├── spec/ │ ├── src/ │ └── yarn.lock
ホスト側から
myfile.txt
ファイルを削除します。コンテナー内において、もう一度
app
ディレクトリの一覧を表示します。 上のファイルがなくなっていることがわかります。root@ac1237fad8db:/src# ls Dockerfile node_modules package.json spec src yarn.lock
Ctrl
+D
を入力して、コンテナーの対話セッションを終了します。
以上がバインドマウントの簡単な実験です。 この操作を通じて、ホストコンテナー間にてファイルが共有される様子や、ファイルへの変更が即座にホストコンテナーの両サイドに反映されることを見てきました。 ここからはバインドマウントを使ってソフトウェアを開発できるようになります。
開発用コンテナー
バインドマウントは、ローカルの開発環境を設定する際にはごく普通に利用するものです。 そして開発用マシンの利点は、ビルドツールや環境すべてをインストールしておく必要はないことです。 単なる docker run コマンドを使うだけで、Docker が依存パッケージやツールをプルしてくれます。
開発用コンテナー内でのアプリ起動
以下の手順を通じて、バインドマウントを利用する開発用コンテナーの起動方法について説明します。 ここでは以下を行います。
- ソースコードをコンテナーに対してマウントします。
- 依存パッケージをすべてインストールします。
- ファイルシステムの変更を検知するために
nodemon
を起動します。
バインドマウントを使ったコンテナーを起動するために CLI または Docker Desktop のいずれかを利用します。
getting-started
が現在実行されていないことを確認します。getting-started-app
ディレクトリにおいて以下のコマンドを実行します。$ docker run -dp 127.0.0.1:3000:3000 \ -w /app --mount type=bind,src="$(pwd)",target=/app \ node:18-alpine \ sh -c "yarn install && yarn run dev"
このコマンドの実行内容は以下のとおりです。
-dp 127.0.0.1:3000:3000
- 以前と同じです。 デタッチ (バックグラウンド) モードにより実行し、ポートマッピングを生成します。-w /app
- "ワーキングディレクトリ" を設定します。 つまりコマンドが実行されるカレントディレクトリを設定するものです。--mount type=bind,src="$(pwd)",target=/app
- ホスト上の現在のディレクトリを、コンテナー内の/app
ディレクトリにバインドマウントします。node:18-alpine
- 利用するイメージです。 アプリ実行のためのベースイメージとしているものであり Dockerfile に指定しています。sh -c "yarn install && yarn run dev"
- 実行するコマンドです。sh
によってシェルを起動しています。 (alpine にbash
はありません。) そしてyarn install
を実行してパッケージのインストールを行うとともにyarn run dev
によって開発サーバーを起動します。package.json
を確認してみればわかりますが、dev
スクリプトがnodemon
を起動しています。
docker logs <container-id>
を実行すればログを見ることができます。 これを見れば、環境がうまく動作できていることがわかるはずです。$ docker logs -f <container-id> nodemon -L src/index.js [nodemon] 2.0.20 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node src/index.js` Using sqlite database at /etc/todos/todo.db Listening on port 3000
ログを見終えたら
Ctrl
+C
を入力して終了します。
getting-started
が現在実行されていないことを確認します。getting-started-app
ディレクトリにおいて以下のコマンドを実行します。$ docker run -dp 127.0.0.1:3000:3000 ` -w /app --mount "type=bind,src=$pwd,target=/app" ` node:18-alpine ` sh -c "yarn install && yarn run dev"
このコマンドの実行内容は以下のとおりです。
-dp 127.0.0.1:3000:3000
- 以前と同じです。 デタッチ (バックグラウンド) モードにより実行し、ポートマッピングを生成します。-w /app
- "ワーキングディレクトリ" を設定します。 つまりコマンドが実行されるカレントディレクトリを設定するものです。--mount "type=bind,src=$pwd,target=/app"
- ホスト上の現在のディレクトリを、コンテナー内の/app
ディレクトリにバインドマウントします。node:18-alpine
- 利用するイメージです。 アプリ実行のためのベースイメージとしているものであり Dockerfile に指定しています。sh -c "yarn install && yarn run dev"
- 実行するコマンドです。sh
によってシェルを起動しています。 (alpine にbash
はありません。) そしてyarn install
を実行してパッケージのインストールを行うとともにyarn run dev
によって開発サーバーを起動します。package.json
を確認してみればわかりますが、dev
スクリプトがnodemon
を起動しています。
docker logs <container-id>
を実行すればログを見ることができます。 これを見れば、環境がうまく動作できていることがわかるはずです。$ docker logs -f <container-id> nodemon -L src/index.js [nodemon] 2.0.20 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node src/index.js` Using sqlite database at /etc/todos/todo.db Listening on port 3000
ログを見終えたら
Ctrl
+C
を入力して終了します。
getting-started
が現在実行されていないことを確認します。getting-started-app
ディレクトリにおいて以下のコマンドを実行します。$ docker run -dp 127.0.0.1:3000:3000 ^ -w /app --mount "type=bind,src=%cd%,target=/app" ^ node:18-alpine ^ sh -c "yarn install && yarn run dev"
このコマンドの実行内容は以下のとおりです。
-dp 127.0.0.1:3000:3000
- 以前と同じです。 デタッチ (バックグラウンド) モードにより実行し、ポートマッピングを生成します。-w /app
- "ワーキングディレクトリ" を設定します。 つまりコマンドが実行されるカレントディレクトリを設定するものです。--mount "type=bind,src=%cd%,target=/app"
- ホスト上の現在のディレクトリを、コンテナー内の/app
ディレクトリにバインドマウントします。node:18-alpine
- 利用するイメージです。 アプリ実行のためのベースイメージとしているものであり Dockerfile に指定しています。sh -c "yarn install && yarn run dev"
- 実行するコマンドです。sh
によってシェルを起動しています。 (alpine にbash
はありません。) そしてyarn install
を実行してパッケージのインストールを行うとともにyarn run dev
によって開発サーバーを起動します。package.json
を確認してみればわかりますが、dev
スクリプトがnodemon
を起動しています。
docker logs <container-id>
を実行すればログを見ることができます。 これを見れば、環境がうまく動作できていることがわかるはずです。$ docker logs -f <container-id> nodemon -L src/index.js [nodemon] 2.0.20 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node src/index.js` Using sqlite database at /etc/todos/todo.db Listening on port 3000
ログを見終えたら
Ctrl
+C
を入力して終了します。
getting-started
が現在実行されていないことを確認します。getting-started-app
ディレクトリにおいて以下のコマンドを実行します。$ docker run -dp 127.0.0.1:3000:3000 \ -w //app --mount type=bind,src="/$(pwd)",target=/app \ node:18-alpine \ sh -c "yarn install && yarn run dev"
このコマンドの実行内容は以下のとおりです。
-dp 127.0.0.1:3000:3000
- 以前と同じです。 デタッチ (バックグラウンド) モードにより実行し、ポートマッピングを生成します。-w //app
- "ワーキングディレクトリ" を設定します。 つまりコマンドが実行されるカレントディレクトリを設定するものです。--mount type=bind,src="/$(pwd)",target=/app
- ホスト上の現在のディレクトリを、コンテナー内の/app
ディレクトリにバインドマウントします。node:18-alpine
- 利用するイメージです。 アプリ実行のためのベースイメージとしているものであり Dockerfile に指定しています。sh -c "yarn install && yarn run dev"
- 実行するコマンドです。sh
によってシェルを起動しています。 (alpine にbash
はありません。) そしてyarn install
を実行してパッケージのインストールを行うとともにyarn run dev
によって開発サーバーを起動します。package.json
を確認してみればわかりますが、dev
スクリプトがnodemon
を起動しています。
docker logs <container-id>
を実行すればログを見ることができます。 これを見れば、環境がうまく動作できていることがわかるはずです。$ docker logs -f <container-id> nodemon -L src/index.js [nodemon] 2.0.20 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node src/index.js` Using sqlite database at /etc/todos/todo.db Listening on port 3000
ログを見終えたら
Ctrl
+C
を入力して終了します。
getting-started
が現在実行されていないことを確認します。
バインドマウントを使うイメージを実行します。
Docker Desktop の最上段の検索欄を選択します。
検索画面にて Images タブを選択します。
検索欄においてコンテナー名として
getting-started
を入力します。情報
検索フィルターを用いてイメージの絞り込みを行えば ローカルイメージ のみを表示することができます。
イメージを選択して Run (実行) をクリックします。
Optional settings (オプション設定) をクリックします。
Host path (ホストパス) に、ホスト上の
getting-started-app
ディレクトリへのパスを入力します。Container path (コンテナーパス) に
/app
を入力します。Run (実行) をクリックします。
Docker Desktop を使ってコンテナーログを確認します。
- Docker Desktop の Containers (コンテナー) を選択します。
- 対象のコンテナーを選択します。
以下のようになっていれば成功です。
nodemon -L src/index.js
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000
開発用コンテナーを使ったアプリ開発
ホスト上のアプリを更新して、その変更内容がコンテナーに反映されることを確認します。
ファイル
src/static/js/app.js
の 109 行めにある "Add Item" (アイテム追加) ボタン名を単に "Add" とします。- {submitting ? 'Adding...' : 'Add Item'} + {submitting ? 'Adding...' : 'Add'}
ファイルを保存します。
ウェブブラウザーのページをリフレッシュします。 変更内容は即座に反映されているのがわかるはずです。 それはバインドマウントを行ったからです。 Nodemon は変更を検知しサーバーを再起動します。 Node サーバーの再起動には数秒程度かかるかもしれません。 もしエラーが発生したら、数秒待ってからリフレッシュを行ってください。
他にも変更したい内容があれば、自由に行ってみてください。 バインドマウントを使っているので、変更を行ってファイルを保存すればコンテナー内にそれが反映されます。 Nodemon が変更を検知したら、コンテナー内のアプリは自動的に再起動します。 確認を終えたら、コンテナーを停止して以下のようにして新たなイメージをビルドしてください。
$ docker build -t getting-started .
まとめ
ここまでにデータベースのデータを保存し、イメージを再ビルドすることなく開発作業を行い、アプリへの変更内容を確認しました。
ボリュームマウントやバインドマウントの他にもタイプの異なるマウントがあります。 それに合わせて、より複雑で特殊なユースケースに対応するストレージドライバーがサポートしています。
関連情報
次のステップ
アプリの運用環境に向けた準備が整ったので、SQLite での作業から、よりスケールアップしたデータベースに移行していくことが必要になります。 要はリレーショナルデータベースを使う方式はそのままに、アプリが利用するデータベースを MySQL に切り替えます。 ではどうやって MySQL を動かしましょう? どうやってコンテナーとの間での通信を実現しましょう? このことを次のセクションで学んでいきます。