Docker コンテナ内で作成・編集した作業ファイルは、コンテナを停止 (docker container stop
) しても消えてしまうことはありませんが、コンテナを削除 (docker container rm
) した場合には消えてしまいます。
これは、コンテナ内のファイル群が、コンテナ内に閉じて存在しているからです(だからこそコンテナなのですが)。
Docker のマウント機能を用いると、コンテナ内の特定のディレクトリパス(の中のファイル群)をホスト PC 上のファイルシステムに関連付けることができるため、コンテナのライフサイクルとは切り離して作業ファイルを管理できるようになります。 マウントには下記で説明する 3 種類がありますが、作業ファイルをホスト PC 側に永続化したい場合は、「ボリュームマウント」か「バインドマウント」というマウントタイプを使用します。 もうひとつの「tmpfs マウント」は、その名のとおりテンポラリファイルにのみ使用できます。
(図は Docker 公式サイトより抜粋)
上記で、「ホスト PC」といっているのは Docker コンテナの実行環境のことであり、Windows や macOS で Docker Desktop を使用している場合は、正確には Linux VM のことを示します。
コンテナ内での作業内容(生成したファイル)をホスト PC 側に永続化したいときに最初に検討すべきは「ボリューム」の使用です。 ボリュームの実体はホスト PC 上のファイルシステム(Docker 管轄下)に永続化されたファイルであり、コンテナ側から見ると「ディレクトリ」として見えます。
ボリュームを明示的に作成するには次のように実行します(コンテナ実行時に自動生成することも可能です)。 ボリューム名を明示するので、名前付きボリューム (named volume) と呼ばれます。
$ docker volume create my-vol
Docker ホスト上に存在しているボリュームの一覧を表示するには、次のように実行します。
$ docker volume ls
DRIVER VOLUME NAME
local e61a091c345b2b969dd288f984be...
local my-vol ← 今回作成したボリューム
local out
(ボリューム名がランダムな記号列になっているものは、匿名マウント時に自動生成されたボリュームです)
指定したボリュームの詳細情報を確認するには次のようにします。
$ 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 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
オプションの src
や dst
でボリューム名やマウント先のパスを指定します。
src=<ボリューム名>
… src
ではなく source
でも OK。存在しないボリューム名を指定すると、その名前のボリュームが自動的に生成されます。src
パラメーターを省略すると、ランダムな16進文字列の名前のボリュームが生成されます。dst=<コンテナ側のパス>
… dst
ではなく destination
や target
でも OK。type=<マウントタイプ>
… ボリュームマウントの場合は省略できます。マウントタイプとして volume
、bind
、tmpfs
を指定します。(過去の記事には、-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 上で上記のパスを探しても見つからないことに注意してください)。
バインドマウントは、ホスト 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 マウントを使用すると、ホスト 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
#