バインドマウントの利用

読む時間の目安: 7 分

バインドマウントは Docker の初期のころから存在しています。 ただし ボリューム に比べると、バインドマウントの機能は限定されます。 バインドマウントを利用すると「ホストマシン」上のファイルやディレクトリがコンテナー内にマウントされます。 そのファイルやディレクトリは、ホストマシン上の絶対パスにより参照できます。 これとは違ってボリュームを利用すると、ホストマシン上に新たなディレクトリが生成され、そこが Docker の保存ディレクトリとなります。 そして Docker はそのディレクトリ内の内容を管理していきます。

そのファイルやディレクトリは、Docker ホストに存在している必要がなくなります。 存在してなかったとしても、必要とされるときには生成されます。 バインドマウントは非常に性能の良いものですが、ただしホストマシンのファイルシステムに依存するものとなり、利用可能な特定のディレクトリ構造に従ったものになります。 今から新たに Docker アプリケーションを開発しようとする場合は、これにかわって 名前つきボリューム の利用を考えてみてください。 バインドマウントを管理するために Docker CLI コマンドを直接利用することはできなくなります。

Docker ホスト上のバインドマウント

-v または --mount フラグの選択

全般に--mountの方がわかりやすいものですが、記述は増えます。 両者の最大の違いは、-vの文法がオプション指定のすべてを 1 項目にとりまとめるものであるのに対して、--mountの文法はそれを 1 つずつ個別に分けている点です。 以下に両フラグにおける文法を比較します。

ヒント: はじめて利用する方は--mountを利用してください。 上級ユーザーは-v--volumeを用いることに慣れているかもしれませんが、--mountを利用するように心がけてください。 --mountの方が簡単に利用することができるとの調査もあります。

  • -vまたは--volume: 3 つの項目から構成され、それぞれをコロン(:)で区切ります。 各項目は正しい順に記述する必要があります。 各項目の意味は、そのときどきによって変わります。
    • バインドマウントの場合、1 つめの項目は ホストマシン 上のファイルまたはディレクトリへのパスです。
    • 2 つめは、コンテナー内にマウントされるファイルまたディレクトリのパスです。
    • 3 つめは任意の指定項目であり、オプション指定をカンマ区切りで指定します。 指定内容にはro, z, Zなどがあります。 このオプションに関しては後に説明しています。
  • --mount: 複数のキーバリューペアを指定し、各ペアはカンマにより区切ります。 そしてそれぞれのペアは<key>=<value>という記述を行います。 --mountにおける記述は-v--volumeにおけるものよりも長くなります。 しかしキーの並び順に意味はなく、このフラグに与えられたキーバリューの内容は容易に理解することができます。
    • typeはマウントのタイプであり、bind, volume, tmpfsといった値を指定します。 ここで説明しているのはバインドマウントであるため、常にbindであるものとします。
    • sourceはマウント元です。 バインドマウントにおいては、Docker デーモンホスト上のファイルまたはディレクトリへのパスになります。 sourceあるいはsrcといった指定がよく用いられます。
    • destinationには、コンテナー上にてマウントするファイルまたはディレクトリのパスを指定します。 destination, dst, targetといった指定がよく用いられます。
    • オプションreadonlyが指定されると、そのバインドマウンドが コンテナーにおける読み込み専用マウント としてマウントされます。
    • オプションbind-propagationが指定されると、バインドプロパゲーション(bind propagation)の設定変更を行います。 rprivate, private, rshared, shared, rslave, slaveのいずれかを指定します。
    • --mountフラグは、selinux ラベルを修正するためのzまたはZオプションには対応していません。

これ以降においては、可能なら--mount-vの両方の例を示していきます。 また先に示すのは--mountとします。

-v--mountの動作の違い

-vおよび--volumeフラグは、長らく Docker の一部分として実現してきているため、その動作を今さら変更することはできません。 このことがつまり、-v--mountの動作の違いの 1 つ になります。

-vまたは--volumeを使ってファイルやディレクトリをバインドマウントした際に、そのファイルやディレクトリが Docker ホスト上にまだ存在していなかった場合、-vはそのマウントエンドポイントを生成します。 その場合には常にディレクトリとして生成されます。

--mountを使ってファイルやディレクトリをバインドマウントした際に、そのファイルやディレクトリが Docker ホスト上に存在していなかった場合、Docker はそのファイルやディレクトリを自動的に生成することはしません。 かわりにエラーが出力されます。

バインドマウントを用いたコンテナーの起動

仮にsourceというディレクトリがあって、そこにソースコードが置かれているとします。 そして成果物はそのサブディレクトリsource/target/に置かれるものとします。 ここでコンテナーが/app/というディレクトリから、その成果物にアクセスできるようにします。 つまり開発ホスト上のソースディレクトリにおいて成果物をビルドしたら、すぐにその最新の成果物をコンテナーがアクセスできるようにする、というものです。 これを実現するには以下のようなコマンドにより、target/ディレクトリをコンテナー内の/app/にバインドマウントします。 コマンドはsourceディレクトリから実行します。 $(pwd)というサブコマンドは、Linux あるいは macOS ホスト上において、カレントディレクトリに展開されます。 Windows ユーザーは、Windows におけるパス変換 も参照してください。

--mount-vによるそれぞれの例は、同一の結果になります。 ただし 2 つの例を同時に実行することはできません。 実行するためには、実行前にdevtestコンテナーを削除しておくことが必要になります。

$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app \
  nginx:latest
$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app \
  nginx:latest

バインドマウントが正しく生成されたかどうかをdocker inspect devtestにより確認します。 出力の中でMountsの項目を見てみます。

"Mounts": [
    {
        "Type": "bind",
        "Source": "/tmp/source/target",
        "Destination": "/app",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
    }
],

この情報から、マウントは「バインド」マウントであることがわかります。 そしてマウント元、マウント先が正しいものであること、マウントが読み書き可能であること、プロパゲーションはrprivateに設定されていることがわかります。

コンテナーを停止します。

$ docker container stop devtest

$ docker container rm devtest

コンテナー上の空ではないディレクトリへのマウント

バインドマウントする先のコンテナー内ディレクトリが空でなかったとします。 このときそのディレクトリ内にはじめからあった内容は、バインドマウントによって見えなくなってしまいます。 そうであっても、このことを便利に利用できる場合もあります。 たとえばアプリケーションの新バージョンをテストする際に、新たなイメージをビルドせずに実現するような場合です。 ただしそういった状況には驚くかもしれません。 またこの動きは Docker ボリューム とは異なるものです。

以下は極端な例です。 コンテナーの/usr/ディレクトリをホストマシン上の/tmp/ディレクトリに置き換えてしまうものです。 おそらくこのコンテナーは使いものにならなくなります。

--mount-vによるそれぞれの例は、同一の結果になります。

$ docker run -d \
  -it \
  --name broken-container \
  --mount type=bind,source=/tmp,target=/usr \
  nginx:latest

docker: Error response from daemon: oci runtime error: container_linux.go:262:
starting container process caused "exec: \"nginx\": executable file not found in $PATH".
$ docker run -d \
  -it \
  --name broken-container \
  -v /tmp:/usr \
  nginx:latest

docker: Error response from daemon: oci runtime error: container_linux.go:262:
starting container process caused "exec: \"nginx\": executable file not found in $PATH".

コンテナーは生成されましたが、起動はされませんでした。 コンテナーはここで削除します。

$ docker container rm broken-container

読み込み専用バインドマウントの利用

開発アプリケーションにおいて、バインドマウントに対してコンテナーから書き込みを行う必要があるものも当然あります。 この場合、その書き込みは Docker ホストに対して行われます。 一方でコンテナーが読み込みアクセスだけを必要とする場合もあります。

以下の例では先ほどと同様ながら、ディレクトリは読み込み専用としてバインドマウントするものです。 以下ではコンテナー内のマウントポイント指定の後に、リスト形式のオプションとして(デフォルトでは何も記述しない箇所に)roを加えます。 複数のオプション指定を行っているので、それぞれをカンマで区切ります。

--mount-vによるそれぞれの例は、同一の結果になります。

$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app,readonly \
  nginx:latest
$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app:ro \
  nginx:latest

バインドマウントが正しく生成されたかどうかをdocker inspect devtestにより確認します。 出力の中でMountsの項目を見てみます。

"Mounts": [
    {
        "Type": "bind",
        "Source": "/tmp/source/target",
        "Destination": "/app",
        "Mode": "ro",
        "RW": false,
        "Propagation": "rprivate"
    }
],

コンテナーを停止します。

$ docker container stop devtest

$ docker container rm devtest

バインドプロパゲーションの設定

バインドマウントとボリュームのいずれにおいても、バインドプロパゲーション(bind propagation)のデフォルトはrprivateです。 この設定はバインドマウントに対してのみ、また Linux ホストマシン上においてのみ変更可能です。 バインドプロパゲーションは高度な技術であるため、多くのユーザーは設定する必要はありません。

バインドプロパゲーションとは、指定されたバインドマウントや名前つきボリュームによって生成されるマウントが、そのレプリカに対して情報伝達(propagate)をするかどうかを表わします。 ここで/mntというマウントポイントを考えます。 これが/tmpにマウントされているとします。 これに対するプロパゲーションの設定は、/tmp/a上のマウントが/mnt/aとして利用可能かどうかを制御するものです。 プロパゲーションの各設定においては、再帰的に対応するマウントポイントが存在します。 再帰的という点でいうと、仮に/tmp/a/fooとしてマウントされていたとします。 このときプロパゲーション設定は/mnt/a/tmp/aが存在するかどうかを定めるものです。

プロパゲーション設定 内容説明
shared マウント元に対するサブマウントは、マウント先にも公開されます。マウント先に対するサブマウントも、マウント元に対して公開されます。
slave shared に類似。ただし一方向のみ。マウント元がサブマウントを公開するなら、マウント先でもこれを見ることができますが、マウント先がサブマウントを公開しても、マウント元からは見ることができません。
private マウントはプライベートなものになります。マウント元におけるサブマウントは、マウント先に公開されません。またマウント先のサブマウントも、マウント元には公開されません。
rshared shared と同様。, but the propagation also extends to and from mount points nested within any of the original or replica mount points.
rslave slave と同様。, but the propagation also extends to and from mount points nested within any of the original or replica mount points.
rprivate デフォルト。private と同様。つまりマウント元やマウント先に対するサブマウントは、どの方向にも伝播(propagate)しません。

マウントポイントに対してバインドプロパゲーションを設定するには、ホストのファイルシステムがバインドプロパゲーションに対応している必要があります。

バインドプロパゲーションの詳細は Linux カーネルドキュメントの shared subtree を参照してください。

以下の例ではtarget/ディレクトリをコンテナーに対して 2 度マウントしています。 そして 2 つめのマウントではroオプションと、バインドプロパゲーションのオプションrslaveを指定しています。

--mount-vによるそれぞれの例は、同一の結果になります。

$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app \
  --mount type=bind,source="$(pwd)"/target,target=/app2,readonly,bind-propagation=rslave \
  nginx:latest
$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app \
  -v "$(pwd)"/target:/app2:ro,rslave \
  nginx:latest

Now if you create /app/foo/, /app2/foo/ also exists.

selinux ラベルの設定

selinuxを利用している場合zまたはZオプションを使って、selinux ラベルを修正することができます。 修正するのは、コンテナーに対してマウントされている ホスト上のファイルやディレクトリ です。 つまりその結果はホストマシンそのもののファイルやディレクトリを変更するため、Docker の範囲外に影響を及ぼします。

  • zオプションは、複数コンテナー間においてバインドマウントされた内容を共有します。
  • Zオプションは、バインドマウントされた内容はプライベートなものであって共有はされません。

このオプションに対しては 最大限 注意してください。 Zオプションを使って、/home/usrなどのようなシステムディレクトリをバインドマウントしてしまうと、ホストマシンは制御できなくなり、ホストマシン上のファイルに対するラベル設定を手動で行うことになります。

重要: バインドマウントをサービス内にて行った場合には、selinux ラベル(:Z:z)は、roと同じように無視されます。 詳しくは moby/moby #32579 を参照してください。

以下はzオプションの指定により、複数コンテナーがバインドマウント先を共有できるようにする例です。

selinux ラベルの修正は--mountフラグでは行うことができません。

$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app:z \
  nginx:latest

次のステップ

storage, persistence, data persistence, mounts, bind mounts