言語を切り替える
テーマを切り替える

Dockerボリューム実戦ガイド:5つの事例で学ぶデータ消失対策

はじめに

午前3時半。

私はターミナルの最後の一行、「Database import completed successfully」を見つめていました。4時間かけて、ようやく2万件のテストデータをMySQLコンテナにインポートし終えたのです。APIをいくつか叩いてテストし、満足して寝ることにしました。

翌朝起きて、習慣的に docker ps を叩いてコンテナの状態を確認しました。空っぽです。胸がざわつきました。「昨夜起動し忘れたか?」 急いで docker ps -a で全コンテナを確認します。見当たりません。ふと思い出しました。寝る前にディスク容量を空けようとして、手癖で docker system prune -a を実行していたのです。

終わった。

全てのデータが消えました。2万件のデータも、4時間の作業も、完全にゼロになりました。

4時間
データ消失

「そんな馬鹿なことする?」と思うかもしれません。正直なところ、当時私はDockerを使い始めて3週間で、コンテナの起動が速くて環境隔離ができることしか知らず、「コンテナ自体はデータの保存に適していない」 というDockerの「アキレス腱」を知りませんでした。コンテナを消せばデータも消え、再起動すれば設定は初期状態に戻るのです。

この記事では、この問題を解決します。5つの実戦的なケーススタディを通じて、Docker Volume(データボリューム)を使ってデータをコンテナから「解放」し、コンテナの生死に関わらずデータを守る方法を、手取り足取り教えます。

コンテナデータ消失の真実

なぜコンテナを消すとデータも消えるのか?

Dockerはレイヤー(層)構造のファイルシステムを使っています。ミルクレープを想像してください。下の層は「イメージ層」(読み取り専用、全コンテナで共有)で、一番上が「コンテナ層」(書き込み可能、各コンテナ専用)です。あなたがコンテナ内で作成したファイル、変更した設定、インポートしたデータは、全てこの一番上の層に書き込まれます。

重要なのはここです:コンテナを削除すると、この書き込み可能な層も一緒に破棄されます。

信じられないなら、試してみましょう:

# Alpineコンテナを起動してファイルを書き込む
docker run -it --name test-container alpine sh
# コンテナ内で実行
echo "重要データ" > /tmp/data.txt
exit

# コンテナを削除
docker rm test-container

# データを探してみる
docker run -it --name test-container alpine sh
cat /tmp/data.txt  # エラー:No such file or directory

データが消える瞬間、警告は一切ありません。

この設計は理にかなっています。コンテナは本来「ステートレス(状態を持たない)アプリケーション」のために設計されました。NginxやAPIサーバーを考えてみてください。再起動しても毎回同じ状態で立ち上がるべきです。しかし、データベース、Redis、ファイルアップロードサービスはどうでしょうか?これらはデータを保持し続けなければなりません。

Docker公式の解決策が Volume(データボリューム) です。データをコンテナの外に出し、コンテナの生死とデータの存続を完全に切り離します。

Volumeとは何か?どうやってデータを救うのか

Volumeの本質:Dockerの「外付けHDD」

Volumeはコンテナに「外付けハードディスク」を繋ぐようなものです。データはコンテナ内ではなく、ホストマシンの特定のディレクトリに書き込まれ、それがコンテナ内部のパスに「マウント(接続)」されます。コンテナからは /var/lib/mysql に見えますが、実際のデータはホストの /var/lib/docker/volumes/mysql-data/_data にあります。

コンテナを削除しても?大丈夫、データはホストに残っています。新しいコンテナを起動して同じVolumeをマウントすれば、データはそのまま戻ってきます。

Dockerには3種類のマウント方式があり、初心者が一番混乱するポイントです:

マウントタイプデータ保存場所適用シナリオ管理方法
VolumeDocker管理ディレクトリ(/var/lib/docker/volumes/)データベース永続化、本番データDockerコマンドで統合管理
Bind Mountホストの任意のパス開発時のコードマウント、設定ファイルパスを手動管理
tmpfsメモリ一時データ、機密情報(ディスクに残さない)コンテナ停止で消去

正直、最初はVolumeとBind Mountの違いが分かりませんでした。「どっちもホストのディレクトリを繋ぐんでしょ?」と。しかし落とし穴がありました。Bind MountでMySQLのデータディレクトリをマウントしていた時、うっかりホスト側のディレクトリを削除してしまい、MySQLコンテナが崩壊しました。

VolumeはDockerが完全に管理するため、パスや権限、バックアップ戦略を気にする必要がありません。データがどこにあるか知りたい? docker volume inspect で分かります。データを移行したい? docker volume コマンドでOKです。これが、公式がBind MountよりVolumeを推奨する理由です。

Volumeデータの実体はどこ?

Linuxシステムでは、全てのVolumeはデフォルトでここにあります:

/var/lib/docker/volumes/<ボリューム名>/_data/

MacやWindowsユーザーは探さないでください。Docker Desktopは仮想マシン内で動いているので、ホストからは直接見えません(docker volume inspect で確認は可能です)。

5つの事例でマスターするVolumeの核心

理論はここまで。実際に手を動かしましょう。これら5つの事例を試せば、基礎から実戦まで完全に理解できます。


事例1:最初の名前付きVolumeを作成する

まずは一番簡単な、空のVolume作成から。

# my-data という名前のVolumeを作成
docker volume create my-data

# 全てのVolumeを表示
docker volume ls

# Volumeの詳細を確認
docker volume inspect my-data

期待される出力(inspectコマンド):

[
    {
        "CreatedAt": "2025-12-17T12:00:00Z",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/my-data/_data",
        "Name": "my-data"
    }
]

Mountpoint が見えますか?これがデータが実際に保存される場所です。


事例2:Nginx静的サイトの永続化

シナリオ:静的サイトを開発中。コードを変えるたびにNginxコンテナを再起動したいが、アップロードした画像やログが消えるのは困る。

解決策:Nginxの /usr/share/nginx/html をVolumeにマウントします。

# コンテンツ保存用Volumeを作成
docker volume create nginx-html

# Nginxコンテナを起動し、Volumeをマウント
docker run -d \
  --name my-nginx \
  -p 8080:80 \
  -v nginx-html:/usr/share/nginx/html \
  nginx:latest

# コンテナに入ってテストページを作成
docker exec my-nginx bash -c 'echo "<h1>Hello Docker Volume!</h1>" > /usr/share/nginx/html/index.html'

# アクセス確認(ブラウザで http://localhost:8080 またはcurl)
curl http://localhost:8080

ここでコンテナを削除します:

docker rm -f my-nginx

新しいコンテナを起動し、同じVolumeをマウントします:

docker run -d \
  --name my-nginx-v2 \
  -p 8080:80 \
  -v nginx-html:/usr/share/nginx/html \
  nginx:latest

# 再度アクセス。内容は残っている!
curl http://localhost:8080

データは消えていません。これがVolumeの魔法です。


事例3:MySQLデータの永続化(実戦級)

これが最も一般的なニーズです。MySQLをコンテナ化するなら、データ永続化は必須です。

# MySQL専用Volumeを作成
docker volume create mysql-data

# MySQLコンテナを起動
docker run -d \
  --name mysql-demo \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  -e MYSQL_DATABASE=testdb \
  -p 3306:3306 \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0

# 起動待ち(約10秒)
sleep 10

# MySQLに接続し、テストテーブルを作成
docker exec -it mysql-demo mysql -uroot -pmy-secret-pw testdb -e "
CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50)
);
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
"

# データ確認
docker exec -it mysql-demo mysql -uroot -pmy-secret-pw testdb -e "SELECT * FROM users;"

コンテナを削除します(誤削除のシミュレーション):

docker rm -f mysql-demo

再起動して同じVolumeをマウント:

docker run -d \
  --name mysql-demo-v2 \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  -p 3306:3306 \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0

# 起動待ち
sleep 10

# データはまだある!
docker exec -it mysql-demo-v2 mysql -uroot -pmy-secret-pw testdb -e "SELECT * FROM users;"

ポイント:MySQLのデータディレクトリは /var/lib/mysql です。ここを必ずマウントしてください。データベースによってパスは異なります(Redisは /data、PostgreSQLは /var/lib/postgresql/data)。


事例4:Redis永続化設定

Redisはデフォルトでデータをメモリに保存しますが、ディスク(RDB/AOF)への永続化も可能です。

# Redisデータ用Volume作成
docker volume create redis-data

# Redis起動(AOF永続化を有効化)
docker run -d \
  --name redis-demo \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:latest redis-server --appendonly yes
  # --appendonly yes が重要

# データを書き込む
docker exec -it redis-demo redis-cli SET mykey "Hello Redis Volume"

# 読み込む
docker exec -it redis-demo redis-cli GET mykey

コンテナ削除:

docker rm -f redis-demo

再起動:

docker run -d \
  --name redis-demo-v2 \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:latest redis-server --appendonly yes

# データは残存
docker exec -it redis-demo-v2 redis-cli GET mykey

注意:Volumeだけでなく --appendonly yes も必要です。そうしないとRedisはデータをディスクに書き出さず、コンテナ再起動でデータが消えます。


事例5:複数コンテナでのVolume共有

シナリオ:あるアプリコンテナがログを書き、別のNginxコンテナがそのログを表示する。2つのコンテナでVolumeを共有します。

# 共有Volume作成
docker volume create shared-logs

# アプリコンテナ起動(ログ書き込み役)
docker run -d \
  --name app-writer \
  -v shared-logs:/logs \
  alpine sh -c "while true; do echo $(date) >> /logs/app.log; sleep 2; done"

# Nginxコンテナ起動(ログ読み込み役)
docker run -d \
  --name log-reader \
  -p 8080:80 \
  -v shared-logs:/usr/share/nginx/html:ro \
  nginx:latest
  # :ro は読み取り専用(Read-Only)。Nginxが誤ってログを変更するのを防ぐ

# 少し待つ
sleep 5

# ログファイルにアクセス(http://localhost:8080/app.log)
curl http://localhost:8080/app.log

ポイント

  1. 1つのVolumeを複数のコンテナで共有可能。
  2. :ro を付けることで読み取り専用にでき、セキュリティが向上する。
  3. 本番環境での「ログ収集コンテナ + アプリコンテナ」構成などでよく使われます。

Volume管理コマンド

5つの事例を終えて、「どうやってVolumeを確認・削除・掃除するの?」と思ったでしょう。
完全な管理コマンドリストです:

# 1. 作成
docker volume create <ボリューム>

# 2. 一覧表示
docker volume ls

# 3. 詳細確認(パスなど)
docker volume inspect <ボリューム>

# 4. 指定削除
docker volume rm <ボリューム>
# 注意:使用中のコンテナがある場合はエラーになります

# 5. 未使用Volumeの一括削除(重要)
docker volume prune
# 確認プロンプトが出ます。yで続行

# 6. 強制一括削除(確認なし)
docker volume prune -f

よくある問題:削除時に “volume is in use” エラーが出る
これはコンテナがまだそのVolumeを使っているためです。
対処法:

# 誰が使っているか確認
docker ps -a --filter volume=<ボリューム>

# コンテナを停止・削除
docker rm -f <コンテナ>

# 再度Volume削除
docker volume rm <ボリューム>

ディスク使用量の確認
Volumeがどれくらい容量を食っているか知りたい時:

# Linux/Mac向け
docker volume inspect <ボリューム> --format '{{ .Mountpoint }}' | xargs du -sh

# 出力例:512M    /var/lib/docker/volumes/mysql-data/_data

Bind Mount vs Volume:どっちを使うべき?

初心者が最も迷うポイントです。私も最初はBind Mountばかり使って失敗しました。

シンプルに覚えましょう:「本番データならVolume、開発コードならBind Mount」

シナリオ推奨方式理由
MySQL/PostgreSQLデータベースVolumeDocker管理で安全、バックアップ容易、高性能
Redis/MongoDB永続化Volume同上
ログ、アップロードファイルVolume誤削除リスクが低い
開発中のソースコードBind Mountホストでコード変えればコンテナに即反映される
設定ファイル(nginx.conf)Bind Mount設定変更のテストが楽
一時データ、キャッシュtmpfs高速、ディスク消費なし

構文比較

# Volume(データ永続化向け)
docker run -v my-volume:/data redis:latest

# Bind Mount(開発環境向け)
docker run -v /Users/me/code:/app node:latest

# 新構文 --mount(より明確、本番推奨)
docker run --mount type=volume,source=my-volume,target=/data redis:latest
docker run --mount type=bind,source=/Users/me/code,target=/app node:latest

意思決定ツリー

迷ったら自問してください:

  1. データを長期保存したい?
    Yes → Volume。 No → tmpfs。

  2. ホスト側で直接ファイルを編集したい?
    Yes → Bind Mount。 No → Volume。

  3. 本番環境?開発環境?
    本番 → Volume。 開発 → Bind Mount。

私の環境の実例

私の開発環境:

# 開発時:コードはBind Mount、DBはVolume
docker run -d \
  --name dev-app \
  -v $(pwd)/src:/app/src \          # コード変更を即反映
  -v app-uploads:/app/uploads \     # アップロードファイル
  -v postgres-data:/var/lib/postgresql/data \  # DBデータ
  my-app:dev

私の本番環境:

# 本番時:全部Volume
docker run -d \
  --name prod-app \
  -v app-uploads:/app/uploads \
  -v postgres-data:/var/lib/postgresql/data \
  my-app:latest
# コードはイメージ内にビルド済みなのでマウント不要

よくある質問とベストプラクティス

FAQ:私の失敗談からの教訓

Q1: Volumeのデータは消えますか?
消えません。あなたが docker volume rm を叩かない限り、ホスト再起動しても残ります。
ただし注意:docker system prune -a --volumes は未使用Volumeを全消去します。慎重に!

Q2: コンテナ起動時にVolumeが無いとどうなる?
Dockerが自動で作ってくれます。

docker run -d -v auto-created-volume:/data alpine
# "auto-created-volume" というVolumeが勝手にできる

でも、明示的に docker volume create する方が管理上おすすめです。

Q3: Volumeはどうバックアップする?
公式推奨の方法:

# 一時コンテナでVolumeをマウントし、tarで固める
docker run --rm \
  -v mysql-data:/source \
  -v $(pwd):/backup \
  alpine tar -czf /backup/mysql-backup.tar.gz -C /source .

復元はその逆(tar -xzf)です。

Q4: 異なるホストへVolumeを移動できる?
はい、手動で。

  1. 元ホストでバックアップ(tar作成)。
  2. ファイルを新ホストへ転送。
  3. 新ホストでVolume作成&解凍。
    NFSやクラウドストレージをバックエンドにする高度なVolumeドライバもあります。

Q5: 匿名Volumeって何?どう掃除する?
名前を指定しないとできる、ランダムな名前のVolumeです。

docker run -d -v /data alpine  # "a1b2c3d4..." みたいな名前になる

消し忘れてディスクを圧迫する原因No.1です。
掃除:docker volume prune
教訓:常に名前付きVolumeを使おう。


6つのベストプラクティス(本番編)

  1. 常に名前付きVolumeを使う
    docker run -v mysql-data:/var/lib/mysql (Good)
    docker run -v /var/lib/mysql (Bad: 匿名Volumeになる)

  2. 重要データは定期バックアップ
    cronなどでDBのVolumeを毎日バックアップしましょう。データ消失の痛みは味わいたくないはずです。

  3. Docker Composeを使う
    複雑な構成もYAMLで管理。

    services:
      db:
        image: mysql:8.0
        volumes:
          - mysql-data:/var/lib/mysql
    volumes:
      mysql-data:
        driver: local
  4. 本番では -v より --mount
    エラー時に「パスが存在しない」など明確に教えてくれるため、少し安全です。

  5. 定期的にVolumeを掃除する
    月に一度は docker volume prune しましょう。

  6. 機密データは暗号化
    Volumeにパスワード等が含まれる場合、ディスク暗号化や暗号化Volumeドライバを検討してください。

結論

冒頭の「午前3時のデータ消失」に戻りましょう。もし当時の私がDocker Volumeを知っていれば、たった一行のコマンドで悲劇は防げました:

docker run -d --name mysql-demo -v mysql-data:/var/lib/mysql mysql:8.0

データはコンテナと共に消えることなく、ホスト上で安全に守られたはずです。

Dockerコンテナは「ステートレス」ですが、あなたのデータは「ステートフル(状態を持つ)」であるべきです。Volumeはその矛盾を解決する架け橋です。コンテナの軽快さと隔離性を享受しつつ、データの安全性も確保できます。

この記事で、作成から実戦(MySQL, Redis)、共有、管理、そしてBind Mountとの使い分けまで、Volumeのほぼ全てを網羅しました。

さあ、今すぐターミナルを開いてください。最初のVolumeを作り、MySQLを起動し、データを書き込み、コンテナを消してみましょう。そして再起動した時、データがそこにある安心感を体験してください。

もし「二度とデータを失いたくない」と思うなら、この記事をブックマークして、いつでも見返せるようにしておいてください。いつかあなたを救うかもしれません。

Docker Volume 実戦フロー

データ消失を防ぐVolumeの作成からMySQL/Redisの永続化、共有までの完全ガイド

⏱️ Estimated time: 30 min

  1. 1

    Step1: 理解:なぜデータが消えるのか

    原因:
    • コンテナの書き込み層はコンテナ削除と共に破棄される。
    • コンテナは再起動で設定が初期化される「使い捨て」設計。
    • Dockerの分層ファイルシステムによる仕様。
  2. 2

    Step2: 事例1:Volume作成の基本

    手順:
    1. docker volume create my-data で作成
    2. docker run -d -v my-data:/data alpine でマウント
    3. これでデータはホスト(/var/lib/docker/volumes/)に保存される
  3. 3

    Step3: 事例2-5:アプリ別永続化設定

    Nginx(静的ファイル):
    docker run -v nginx-html:/usr/share/nginx/html nginx

    MySQL(DBデータ):
    docker run -v mysql-data:/var/lib/mysql mysql:8.0

    Redis(KVSデータ):
    docker run -v redis-data:/data redis redis-server --appendonly yes

    共有Volume:
    docker run -v shared-data:/data container1
    docker run -v shared-data:/data container2
  4. 4

    Step4: 選択:VolumeかBind Mountか

    基準:
    • 本番データ、DB → 【Volume】(安全、高性能、Docker管理)
    • 開発コード、設定ファイル → 【Bind Mount】(ホストで編集即反映)
    • 一時データ → 【tmpfs】(メモリ、高速)

FAQ

コンテナを削除するとデータも消えますか?
はい、デフォルトでは消えます。コンテナ内のファイルシステムはコンテナと共に破棄されるからです。これを防ぐには「Docker Volume」を使用してデータをホスト側に保存(永続化)する必要があります。
VolumeとBind Mountの違いは何ですか?
VolumeはDockerが管理する領域(/var/lib/docker/volumes/)に保存され、安全で高性能、本番環境向きです。Bind Mountはホストの任意のパスをマウントする方法で、ファイルを直接編集できるため開発環境向きです。
MySQLやRedisのデータを永続化するには?
コンテナ起動時に `-v` オプションで適切なデータディレクトリをマウントします。MySQLなら `/var/lib/mysql`、Redisなら `/data` です。Redisの場合はさらに `--appendonly yes` オプションでディスク書き込みを有効にする必要があります。
Bind MountとVolume、どちらを使うべき?
シンプルに、「本番データはVolume、開発コードはBind Mount」と覚えてください。データベースやログはVolumeへ、開発中のソースコードや頻繁に変える設定ファイルはBind Mountが適しています。

5 min read · 公開日: 2025年12月17日 · 更新日: 2026年1月22日

コメント

GitHubアカウントでログインしてコメントできます

関連記事