🐧 Linux 総合学習プラットフォーム
コンテナ Docker/Podman ・ 中級

ボリュームによる永続化

コンテナは消すと中のデータも一緒に消えます。データベースのファイルなど残したいものは、ボリュームを使ってコンテナの外に保存します。docker run の -v オプションで、ホスト側のディレクトリやボリュームをコンテナ内のパスに対応づけます。こうしておけばコンテナを作り直してもデータは保持され、別のコンテナから同じデータを参照することもできます。

コンテナを実用に使い始めると、ある日、誰もが冷や汗をかく瞬間に出くわす。コンテナを作り直したとたん、中のデータがまるごと消えていた、という事故だ。

なぜこんなことが起きるのか。そして、どうすれば防げるのか。原因を正しく理解し、対策としてのボリュームの使い方を身につけておくことが、コンテナを安心して使うための土台になる。

まずは「なぜ消えるのか」から押さえよう。ここを知らずにデータベースなどを動かすと、いずれ必ずこの落とし穴を踏むことになる。

💥 なぜ、コンテナを消すとデータまで消えるのか

前のトピックで触れたとおり、コンテナはイメージという読み取り専用の雛形の上に、書き込み用の薄い層を1枚かぶせて動いている。コンテナの中で作ったファイルや書き換えた内容は、すべてこの最上層に記録される。

ところが、docker rm でコンテナを削除すると、この書き込み層ごと消えてなくなる。これが冒頭の事故の正体だ。

💡
ポイントコンテナ内のデータは書き込み層に記録される。docker rm で消すとその層ごと消える。これがデータ消失の正体。
逃がさない場合コンテナ(書き込み層)データもこの中docker rmデータも一緒に消えるボリュームで逃がすコンテナ-vホスト側 / ボリューム消しても外に残る

つまりコンテナの中身は、原則として「使い捨ての作業スペース」なのである。動作確認の一時データならそれで構わない。だが、データベースのファイルや、利用者がアップロードした画像のように消えては困るものは、コンテナの外に逃がして生き残らせる必要がある。これをデータの永続化と呼ぶ。

🗄 消えては困るデータを逃がす――ボリューム

永続化の手段が、コンテナの外にデータを保存するボリュームの仕組みだ。docker run の -v オプションを使い、「ホスト側のどこか」と「コンテナ内のどこか」を対応づけて、データの実体をコンテナの外に置く。

書式は -v ホスト側:コンテナ側 で、区切りはコロンだ。たとえば docker run -d --name web -v /home/user01/data:/usr/share/nginx/html nginx とすると、ホストの /home/user01/data がコンテナ内の /usr/share/nginx/html に結びつく。

docker run -d --name web -v /home/user01/data:/usr/share/nginx/html nginx ……-v でホスト側とコンテナ側を結ぶ。

こうしておけば、ホスト側のフォルダに置いたファイルはコンテナから見える。逆にコンテナがそこへ書いた内容もホスト側に残る。

コンテナを docker rm で消しても、ホスト側のデータはそのまま残る。だから、また同じ -v を付けてコンテナを作り直せば、続きから使える。これで冒頭の事故は起きなくなる。

いま見たのは「ホストのフォルダを直接つなぐ」やり方だが、これには名前がある。バインドマウントだ。

これとは別に、docker が管理する専用の保管領域を使う「名前付きボリューム」もある。docker volume create mydata で作り、docker run -v mydata:/var/lib/mysql … のようにボリューム名を左側に書いて使う。

違いはざっくり言えばこうだ。バインドマウントは置き場所をホスト上のパスで自分が決める方式、名前付きボリュームは置き場所を docker に任せて名前だけで扱う方式だ。

🔗
たとえバインドマウントは置き場所を自分で決める方式、名前付きボリュームは置き場所を docker に任せて名前だけで扱う方式。

設定ファイルやソースをホスト側で編集しながら使いたいときはバインドマウントが向いている。データベースの中身のように docker に任せておけばよいものは名前付きボリュームが向いている。作成済みのボリュームは docker volume ls で一覧できる。

🔀 つないだのに見えない――どこを間違えたか

最も多い失敗が、コロンの左右を逆に書いてしまうことだ。-v は必ず ホスト側:コンテナ側 の順だ。

つまずき最多の失敗はコロンの左右逆。-v は必ず ホスト側:コンテナ側。「左がホスト、右がコンテナ」を守る。

これを逆にすると意図しないフォルダがマウント先になる。見えるはずのデータが見えなかったり、空のフォルダで上書きされたように見えたりする。「左がホスト、右がコンテナ」を必ず守ってほしい。

もう1つ、相対パスを書くと予期しない場所が使われることがある。バインドマウントのホスト側は /home/user01/data のような絶対パスで指定するのが安全だ。

RHEL系(MiracleLinux など)では、左右も絶対パスも正しいのに弾かれる、という一段深い落とし穴がある。SELinux が有効な環境では、ホストのフォルダをそのままマウントしてもコンテナ側から「許可がない(Permission denied)」と弾かれることがあるのだ。

その場合は、マウント指定の末尾に :Z を足して -v /home/user01/data:/usr/share/nginx/html:Z のように書く。SELinux のラベルが調整されてアクセスできるようになる。「ボリュームをつないだのに中が読めない・書けない」ときは、まずこの SELinux のラベルを疑うのが、RHEL系での定石だ。

コツRHEL系で Permission denied なら、末尾に :Z を足す(-v …:/…:Z)。SELinux のラベルが調整される。

👥 1つのデータを複数で使い、確かめる

ボリュームのもう1つの利点は、複数のコンテナから同じデータを参照できることだ。同じボリュームを複数のコンテナに -v で割り当てれば、片方が書いた内容をもう片方が読む、といった共有ができる。

たとえば、データを書き込む処理用のコンテナと、それを読み出して配信するコンテナで、同じ保存領域を共有する、という構成が組める。

マウントが意図どおりにできているかは、docker exec でコンテナに入り、対象パスの中身を ls で確認するのが手早い。

読み取り専用で渡したいときは、マウント指定の末尾に :ro を付ける。-v /home/user01/conf:/etc/app:ro のようにすると、コンテナ側からは読めるが書き換えられない状態にできる。設定ファイルを誤って書き換えられる事故を防げる。

コツ末尾に :ro を付けると読み取り専用。-v /home/user01/conf:/etc/app:ro で設定ファイルの誤書き換えを防げる。

名前付きボリュームを使った場合、その中身がホストの実際のどこに保存されているかは、docker volume inspect ボリューム名 で確認できる(Mountpoint という項目にパスが出る)。

とはいえ、名前付きボリュームは docker に管理を任せるのが本来の使い方だ。保存先のパスを直接いじるのは避け、データの出し入れはコンテナ経由で行うのが安全だ。

使わなくなったボリュームはコンテナを消しても自動では消えず残り続ける。不要なものは docker volume rm ボリューム名 で明示的に削除する。どのボリュームもどのコンテナからも使われていない状態をまとめて掃除したいときは docker volume prune が使えるが、消すと戻せないので、対象をよく確かめてから実行してほしい。

⚖ 消えていいものと、消えてはいけないもの

実務では、コンテナは消えてもいいもの、データは消えてはいけないもの、という線引きをはっきり意識する。

💡
ポイントコンテナは使い捨て、大事なデータはボリュームで外へ。この線引きを守れば、データ消失はほぼ防げる。

アプリのコンテナ自体は気軽に作り直す。一方、データベースの保存先や設定ファイル、アップロードされたファイルといった「残すべきもの」は必ずボリュームで外に出しておく。これが鉄則だ。こうしておけば、アプリのバージョンアップでコンテナを作り直しても、データはそのまま引き継げる。

開発中は、ホスト側のソースコードのフォルダをバインドマウントしてコンテナにつなぎ、エディタでホスト側を編集すると即座にコンテナへ反映される、という使い方も定番だ。「コンテナは使い捨て、大事なデータはボリュームで外へ」と覚えておけば、データ消失の事故はほぼ防げる。

この項目に出てくる用語

ボリュームぼりゅーむ
コンテナの外にデータを保存する仕組み。
コンテナこんてな
イメージから起動した、実行中のアプリの実体。

関連コマンド

docker rundocker volume

▶ 学習アプリでこの続きを学ぶ・演習する