バインドマウントの利用

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 が決めますユーザーが決めます
コンテナー内容の別のボリュームによる公開不可
ボリュームドライバーのサポート有り無し

バインドマウントを試す

アプリケーションの開発に向けてバインドマウントをどのように活用していけば良いのか。 そういったことを学ぶ前に、まずは単純な実験を通じて、バインドマウントがどのように動作するのかを実践で理解していくことにしましょう。

  1. ここで確認しておくことですが、getting-started-app ディレクトリは Docker Desktop のファイル共有設定において定義されたディレクトリ内にあることが必要です。 この設定は、コンテナーとの間で共有するのがファイルシステムのどこであるかを定義します。 その設定へのアクセス方法の詳細については ファイル共有 を参照してください。

  2. 端末を開いて getting-started-app ディレクトリに移動します。

  3. 以下のコマンドを実行して 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) のことです。

  4. コマンド実行後 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
    
  5. src ディレクトリに移動します。

    このディレクトリは、コンテナーが起動した際にマウント先としたディレクトリです。 このディレクトリ内の内容一覧を確認すれば、ホストマシン上の getting-started-app ディレクトリ内にあるファイルとまったく同じであることがわかります。

    root@ac1237fad8db:/# cd src
    root@ac1237fad8db:/src# ls
    Dockerfile  node_modules  package.json  spec  src  yarn.lock
    
  6. myfile.txt というファイルを新たに生成します。

    root@ac1237fad8db:/src# touch myfile.txt
    root@ac1237fad8db:/src# ls
    Dockerfile  myfile.txt  node_modules  package.json  spec  src  yarn.lock
    
  7. ホスト上において getting-started-app ディレクトリを開きます。 確かに myfile.txt がそのディレクトリ内に存在しています。

    ├── getting-started-app/
    │ ├── Dockerfile
    │ ├── myfile.txt
    │ ├── node_modules/
    │ ├── package.json
    │ ├── spec/
    │ ├── src/
    │ └── yarn.lock
  8. ホスト側から myfile.txt ファイルを削除します。

  9. コンテナー内において、もう一度 app ディレクトリの一覧を表示します。 上のファイルがなくなっていることがわかります。

    root@ac1237fad8db:/src# ls
    Dockerfile  node_modules  package.json  spec  src  yarn.lock
    
  10. Ctrl + D を入力して、コンテナーの対話セッションを終了します。

以上がバインドマウントの簡単な実験です。 この操作を通じて、ホストコンテナー間にてファイルが共有される様子や、ファイルへの変更が即座にホストコンテナーの両サイドに反映されることを見てきました。 ここからはバインドマウントを使ってソフトウェアを開発できるようになります。

開発用コンテナー

バインドマウントは、ローカルの開発環境を設定する際にはごく普通に利用するものです。 そして開発用マシンの利点は、ビルドツールや環境すべてをインストールしておく必要はないことです。 単なる docker run コマンドを使うだけで、Docker が依存パッケージやツールをプルしてくれます。

開発用コンテナー内でのアプリ起動

以下の手順を通じて、バインドマウントを利用する開発用コンテナーの起動方法について説明します。 ここでは以下を行います。

  • ソースコードをコンテナーに対してマウントします。
  • 依存パッケージをすべてインストールします。
  • ファイルシステムの変更を検知するために nodemon を起動します。

バインドマウントを使ったコンテナーを起動するために CLI または Docker Desktop のいずれかを利用します。


  1. getting-started が現在実行されていないことを確認します。

  2. 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 を起動しています。
  3. 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 を入力して終了します。

  1. getting-started が現在実行されていないことを確認します。

  2. 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 を起動しています。
  3. 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 を入力して終了します。

  1. getting-started が現在実行されていないことを確認します。

  2. 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 を起動しています。
  3. 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 を入力して終了します。

  1. getting-started が現在実行されていないことを確認します。

  2. 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 を起動しています。
  3. 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 が現在実行されていないことを確認します。

バインドマウントを使うイメージを実行します。

  1. Docker Desktop の最上段の検索欄を選択します。

  2. 検索画面にて Images タブを選択します。

  3. 検索欄においてコンテナー名として getting-started を入力します。

    情報

    検索フィルターを用いてイメージの絞り込みを行えば ローカルイメージ のみを表示することができます。

  4. イメージを選択して Run (実行) をクリックします。

  5. Optional settings (オプション設定) をクリックします。

  6. Host path (ホストパス) に、ホスト上の getting-started-app ディレクトリへのパスを入力します。

  7. Container path (コンテナーパス) に /app を入力します。

  8. Run (実行) をクリックします。

Docker Desktop を使ってコンテナーログを確認します。

  1. Docker Desktop の Containers (コンテナー) を選択します。
  2. 対象のコンテナーを選択します。

以下のようになっていれば成功です。

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

開発用コンテナーを使ったアプリ開発

ホスト上のアプリを更新して、その変更内容がコンテナーに反映されることを確認します。

  1. ファイル src/static/js/app.js の 109 行めにある "Add Item" (アイテム追加) ボタン名を単に "Add" とします。

    - {submitting ? 'Adding...' : 'Add Item'}
    + {submitting ? 'Adding...' : 'Add'}
    

    ファイルを保存します。

  2. ウェブブラウザーのページをリフレッシュします。 変更内容は即座に反映されているのがわかるはずです。 それはバインドマウントを行ったからです。 Nodemon は変更を検知しサーバーを再起動します。 Node サーバーの再起動には数秒程度かかるかもしれません。 もしエラーが発生したら、数秒待ってからリフレッシュを行ってください。

    Add ボタンのラベルを変更したスクリーンショット
  3. 他にも変更したい内容があれば、自由に行ってみてください。 バインドマウントを使っているので、変更を行ってファイルを保存すればコンテナー内にそれが反映されます。 Nodemon が変更を検知したら、コンテナー内のアプリは自動的に再起動します。 確認を終えたら、コンテナーを停止して以下のようにして新たなイメージをビルドしてください。

    $ docker build -t getting-started .
    

まとめ

ここまでにデータベースのデータを保存し、イメージを再ビルドすることなく開発作業を行い、アプリへの変更内容を確認しました。

ボリュームマウントやバインドマウントの他にもタイプの異なるマウントがあります。 それに合わせて、より複雑で特殊なユースケースに対応するストレージドライバーがサポートしています。

関連情報

次のステップ

アプリの運用環境に向けた準備が整ったので、SQLite での作業から、よりスケールアップしたデータベースに移行していくことが必要になります。 要はリレーショナルデータベースを使う方式はそのままに、アプリが利用するデータベースを MySQL に切り替えます。 ではどうやって MySQL を動かしましょう? どうやってコンテナーとの間での通信を実現しましょう? このことを次のセクションで学んでいきます。