Docker のマウント機能でファイルを永続化する(ボリュームマウント、バインドマウント、tmpfs マウント)

Docker の 3 種類のマウント

Docker コンテナ内で作成・編集した作業ファイルは、コンテナを停止 (docker container stop) しても消えてしまうことはありませんが、コンテナを削除 (docker container rm) した場合には消えてしまいます。 これは、コンテナ内のファイル群が、コンテナ内に閉じて存在しているからです(だからこそコンテナなのですが)。

Docker のマウント機能を用いると、コンテナ内の特定のディレクトリパス(の中のファイル群)をホスト PC 上のファイルシステムに関連付けることができるため、コンテナのライフサイクルとは切り離して作業ファイルを管理できるようになります。 マウントには下記で説明する 3 種類がありますが、作業ファイルをホスト PC 側に永続化したい場合は、「ボリュームマウント」か「バインドマウント」というマウントタイプを使用します。 もうひとつの「tmpfs マウント」は、その名のとおりテンポラリファイルにのみ使用できます。

/p/hxhzgxf/img-001.png
図: Docker の 3 種類のマウント(公式サイトより)
ボリュームマウント
ホスト PC 上にコンテナ用のデータファイルを作成 し、コンテナ内の特定のディレクトリパスにマッピングします。 このデータファイルはポータビリティが高く、クラウド上に保存するということもできます(ボリュームドライバーが必要)。 複数のコンテナから 1 つのボリュームを共有することも可能です。 コンテナ内で作成したファイルの永続化には、このボリュームの使用が推奨されています。
バインドマウント
ホスト PC の特定のディレクトリ(絶対パス指定) を、コンテナ内の特定のディレクトリパスにマッピングします。 ボリュームと比べてポータビリティが低いため、名前付きボリュームの使用が推奨されてます。例えば、バインドマウントでは、ホスト側の多数のファイルとマッピングされてしまうため別環境に移しにくいとか、マウント時のパス表現が OS に依存してしまうといった欠点があります。 ホスト側からコンテナで操作したファイルをささっと覗いて見たいときはバインドマウントは便利ですが、これはコンテナ内で作成した危険なファイルが、そのままホスト上にも作られてしまうということを示しています。
tmpfs マウント
ホスト PC のメモリ領域 を、コンテナ内の特定のディレクトリパスにマッピングします。コンテナ上でファイル生成を行うと、実際には一時的なメモリ領域に保存されることになるので、ここに保存されたファイルはコンテナを停止すると消えてしまいます。一時的なファイルを格納するディレクトリを tmpfs マウントすることで、コンテナサイズの増加を防ぐことができ(書き込みレイヤーに出力されない)、パフォーマンスの向上を見込めます。

上記で、「ホスト PC」といっているのは Docker コンテナの実行環境のことであり、Windows や macOS で Docker Desktop を使用している場合は、正確には Linux VM のことを示します。

ボリュームマウント (volume mount) の使い方

コンテナ内での作業内容(生成したファイル)をホスト PC 側に永続化したいときに最初に検討すべきは「ボリューム」の使用です。 ボリュームの実体はホスト PC 上のファイルシステム(Docker 管轄下)に永続化されたファイルであり、コンテナ側から見ると「ディレクトリ」として見えます。

ボリュームを作成する (docker volume create)

ボリュームを明示的に作成するには次のように実行します(コンテナ実行時に自動生成することも可能です)。 ボリューム名を明示するので、名前付きボリューム (named volume) と呼ばれています。

$ docker volume create my-vol

ボリュームの一覧を表示する (docker volume ls)

Docker ホスト上に存在しているボリュームの一覧を表示するには、次のように実行します。

$ docker volume ls
DRIVER    VOLUME NAME
local     e61a091c345b2b969dd288f984be...
local     my-vol  ← 今回作成したボリューム
local     out

(ボリューム名がランダムな記号列になっているものは、匿名マウント時に自動生成されたボリュームです)

ボリュームの詳細情報を表示する (docker volume inspect)

指定したボリュームの詳細情報を確認するには次のようにします。

$ docker volume inspect my-vol
[
    {
        "CreatedAt": "2022-01-25T07:18:24Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": null,
        "Scope": "local"
    }
]

ボリュームを削除する (docker volume rm/prune)

# ボリューム名を指定して削除
$ docker volume rm my-vol

# 未使用のボリューム(どのコンテナからも参照されていないもの)をすべて削除
$ docker volume prune

ボリュームマウントしてコンテナを起動する

コンテナ側の /volume というパスに、上記で作成した my-vol ボリュームをマウントして使ってみます。 ここでは、軽量の Alpine Linux イメージ (alpine:latest) を使ってコンテナを作成します。

# コンテナ (my-alpine) の作成
$ docker container create -it --mount src=my-vol,dst=/volume --name my-alpine alpine:latest

# コンテナが作成できているか確認
$ docker container ls -a

--mount オプションの srcdst で、ボリューム名やマウント先のパスを指定します。

  • src=<ボリューム名>src ではなく source でも OK。存在しないボリューム名を指定すると、その名前のボリュームが自動的に生成されます。src パラメーターを省略すると、ランダムな16進文字列の名前のボリュームが生成されます。
  • dst=<コンテナ側のパス>dst ではなく destinationtarget でも OK。
  • type=<マウントタイプ>ボリュームマウントの場合は省略できます。マウントタイプとして volumebindtmpfs を指定します。

(過去の記事には、-v オプションを使っているものもありますが、現在は公式に --mount オプションの使用が推奨されています(挙動がわかりにくく問題が発生しやすいなどの理由があります)。特別な事情がない限り、--mount オプションの方を使うようにしてください)

コンテナを作成したら、次のようにして起動してシェル接続できます。

$ docker start -ai my-alpine

コンテナ側の /volume というディレクトリが見えているかを確認してください。 この中にファイルを作成すると、ホスト OS 側のボリューム領域に内容が保存されるので、次回コンテナを起動 (docker start) したときにファイル編集作業の続きを行えます。 my-volume ボリュームを他のコンテナにマウントして共有するということもできます。

# ls /volume  # 初期状態は空っぽ
# echo Hello > /volume/hello.txt
# exit

ボリュームファイルの格納場所

ボリュームのデータファイルは具体的にはホスト PC の /var/lib/docker/volumes に生成されますが、ボリュームは docker volume コマンドを介して操作するので、通常は保存先のパスを意識する必要はありません。 Windows や macOS で Docker Desktop を使用している場合は、Volumes タブでボリュームの一覧を確認することができます(Docker Desktop のバックエンドとして使われる Linux VM 上に格納されているため、Windows や macOS 上で上記のパスを探しても見つからないことに注意してください)。

/p/hxhzgxf/img-002.png
図: Docker Desktop によるボリュームの確認

バインドマウント (bind mount) の使い方

バインドマウントは、ホスト OS 上のディレクトリをコンテナ側のディレクトリにマッピングします。 つまり、お互いのファイルシステム上で同じディレクトリ/ファイルを参照できるようになります。 次の例では、ホスト OS 上のカレントディレクトリにある data ディレクトリを、コンテナ側の /data にマウントしています。 ホスト OS 側のディレクトリは、存在するディレクトリを絶対パスで指定する必要があります(次の例では、"$(pwd)/data" で絶対パスを生成しています)。

$ mkdir data
$ docker container create -it --mount type=bind,src="$(pwd)/data",dst=/data --name my-alpine alpine:latest

バインドマウントするときは、上記のように --mount オプションで type=bind と指定する必要があります。

コンテナを起動してシェル接続し、適当なファイルを /data ディレクトリ以下に作成してみます。

$ docker container start -ai my-alpine

# echo Hello > /data/hello.txt
# exit

ホスト OS 側で data ディレクトリを見ると、コンテナで作成したファイルが存在していることを確認できます。

$ ls data
hello.txt

tmpfs マウントの使い方

tmpfs マウントを使用すると、ホスト OS 上のメモリ領域を一時ファイルシステムとして、コンテナ側のディレクトリにマッピングすることができます。 結果として、そのディレクトリ内に保存したファイルは、コンテナを停止したときに削除されます。 一時的にしか使用しないファイルは、tmpfs マウントしたディレクトリ内に作成することで、Docker イメージの肥大化を防ぐことができます。 また、そのファイルはメモリ上に生成されるため、パフォーマンス的にも有利です。

$ docker container create -it --mount type=tmpfs,dst=/sandbox --name my-alpine alpine:latest

tmpfs マウントするときは、上記のように --mount オプションで type=tmpfs と指定する必要があります。 この例では、コンテナ側の /sandbox ディレクトリを tmpfs マウントのターゲットとしています。

コンテナを起動して /sandbox 内にファイルを作成し、コンテナを停止してみます。

$ docker start -ai my-alpine

# echo Hello > /sandbox/hello.txt
# exit

再度コンテナを起動して /sandbox の中を見ると、上記で作成した hello.txt は消えていることがわかります。

$ docker start -ai my-alpine

# ls /sandbox
#