そんな失敗、Dockerを使い始めて一度は経験するはずです。コンテナはプロセスと同じで、停止・削除すると内部に書き込んだデータはすべて消えます。
この記事では、Dockerのボリューム(docker volume)とバインドマウントの仕組みを基礎から解説し、コンテナが消えてもデータを安全に永続化するための実践的な設計手順を紹介します。
Docker Composeでのvolumes定義まで、実際のサーバー出力例を交えて説明します。
動作確認環境: Rocky Linux 9.4 / Ubuntu 24.04 LTS(Docker Engine 26.x・Docker Compose Plugin v2.27)
この記事のポイント
・コンテナのデータはデフォルトで揮発性。永続化にはvolumeかバインドマウントを使う
・docker volume createで名前付きボリュームを作り、-vオプションでコンテナに接続する
・バインドマウントはホストのディレクトリを直接マウントし、開発環境のソース共有に向く
・Docker ComposeではvolumesセクションでDB・アプリのデータを永続化して管理する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜコンテナのデータは消えるのか
コンテナは「イメージを元に作った使い捨てプロセス」です。コンテナを起動すると、イメージの読み取り専用レイヤーの上に「書き込み可能レイヤー(コンテナレイヤー)」が追加されます。
MySQLがデータを書き込む場所も、Webアプリがアップロードしたファイルも、すべてこのコンテナレイヤーに保存されます。
そして docker rm でコンテナを削除すると、コンテナレイヤーも一緒に消えます。
つまり、データはコンテナの寿命と紐付いているのです。
この問題を解決するDockerの仕組みが、ボリューム(Volume)とバインドマウント(Bind Mount)です。
データ永続化の2つのアプローチ比較
| 方式 | データの保存場所 | 主な用途 |
|---|---|---|
| Dockerボリューム | /var/lib/docker/volumes/ 配下(Docker管理) | DBデータ・ステートフルなアプリの永続化 |
| バインドマウント | ホストの任意のパス(ユーザー管理) | 開発時のソースコード共有・設定ファイルの注入 |
Dockerボリュームの基本的な使い方
1. ボリュームを作成する
名前付きボリュームは docker volume create で作成します。# 名前付きボリュームを作成する $ docker volume create mydb_data # ボリューム一覧を確認する $ docker volume ls DRIVER VOLUME NAME local mydb_data # ボリュームの詳細情報を確認する $ docker volume inspect mydb_data [ { "CreatedAt": "2026-07-01T09:00:00+09:00", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/mydb_data/_data", "Name": "mydb_data", "Options": {}, "Scope": "local" } ]
コンテナはこのパスに書き込みますが、コンテナを削除してもこのディレクトリはホスト上に残ります。
2. コンテナ起動時にボリュームをマウントする
-v オプション(または --mount オプション)でコンテナとボリュームを接続します。# -vオプション: "ボリューム名:コンテナ内パス" の形式で指定する $ docker run -d \ --name mysql_test \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=testdb \ -v mydb_data:/var/lib/mysql \ mysql:8.0 # コンテナが起動していることを確認する $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1b2c3d4e5f6 mysql:8.0 "docker-entrypoint.s…" 30 seconds ago Up 29 seconds 3306/tcp, 33060/tcp mysql_test
3. データが永続化されることを確認する
実際にコンテナを削除→再作成してもデータが残ることを確認しましょう。# MySQLにテーブルを作成してデータを書き込む $ docker exec -it mysql_test mysql -uroot -psecret testdb -e \ "CREATE TABLE users (id INT, name VARCHAR(50)); INSERT INTO users VALUES (1, 'Miyazaki');" # コンテナを停止・削除する(データが消えるはず?) $ docker stop mysql_test $ docker rm mysql_test # 同じボリュームを使って新しいコンテナを起動する $ docker run -d \ --name mysql_new \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=testdb \ -v mydb_data:/var/lib/mysql \ mysql:8.0 # データが残っているか確認する $ docker exec -it mysql_new mysql -uroot -psecret testdb -e "SELECT * FROM users;" +------+----------+ | id | name | +------+----------+ | 1 | Miyazaki | +------+----------+ 1 row in set (0.00 sec)
これが名前付きボリュームの最大のメリットです。
バインドマウントの使い方
バインドマウントは、ホストの任意のディレクトリをコンテナ内にマウントする方法です。ホスト側のファイルをコンテナ内から直接読み書きできるため、開発時のソースコード共有や設定ファイルの注入に向いています。
1. バインドマウントでNginxの設定を注入する
# ホスト側に設定ファイルと公開ディレクトリを用意する $ mkdir -p ~/nginx-demo/html ~/nginx-demo/conf.d $ echo "
Hello from Docker Bind Mount
" > ~/nginx-demo/html/index.html # バインドマウントでNginxコンテナを起動する # -v "ホストの絶対パス:コンテナ内パス" の形式で指定する $ docker run -d \ --name nginx_demo \ -p 8080:80 \ -v ~/nginx-demo/html:/usr/share/nginx/html:ro \ nginx:1.26 # ホスト側でindex.htmlを更新するとコンテナ側にも即時反映される $ echo "Updated!
" > ~/nginx-demo/html/index.html $ curl http://localhost:8080Updated!
これが開発環境でバインドマウントが重宝される理由です。
2. ボリュームとバインドマウントの使い分け指針
・Dockerボリューム推奨:DBデータ(MySQL・PostgreSQL)、アップロードファイル、本番環境全般・バインドマウント推奨:開発中のソースコード共有、設定ファイルの注入(nginx.conf・php.ini等)
・本番環境でバインドマウントを使う場合:ホストパスの権限管理を厳格に。:roを付けて読み取り専用にする
Docker ComposeでVolumesを設計する
実務ではdocker runを直接使うより、Docker Composeでサービスを定義する場面が多いです。Composeでのボリューム設計を押さえておきましょう。
1. compose.ymlでボリュームを定義する
# ~/myapp/compose.yml services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: appdb volumes: # 名前付きボリュームをマウントする - db_data:/var/lib/mysql restart: always web: image: nginx:1.26 ports: - "80:80" volumes: # バインドマウント(開発用ソースコード) - ./html:/usr/share/nginx/html:ro # 設定ファイルをホストから注入する - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - db # トップレベルのvolumesセクションで名前付きボリュームを宣言する volumes: db_data:
2. Composeを起動してボリュームを確認する
# コンテナを起動する $ docker compose up -d [+] Running 3/3 ✔ Volume "myapp_db_data" Created ✔ Container myapp-db-1 Started ✔ Container myapp-web-1 Started # 作成されたボリュームを確認する(プロジェクト名_ボリューム名 の命名規則) $ docker volume ls DRIVER VOLUME NAME local myapp_db_data # コンテナのマウント状況を確認する $ docker inspect myapp-db-1 | grep -A 10 '"Mounts"' "Mounts": [ { "Type": "volume", "Name": "myapp_db_data", "Source": "/var/lib/docker/volumes/myapp_db_data/_data", "Destination": "/var/lib/mysql", "Driver": "local", "Mode": "z", "RW": true, "Propagation": "" } ],
3. docker compose downでもボリュームを残す
# docker compose downはデフォルトでボリュームを削除しない $ docker compose down [+] Running 3/3 ✔ Container myapp-web-1 Removed ✔ Container myapp-db-1 Removed ✔ Network myapp_default Removed # ボリュームはまだ残っている $ docker volume ls DRIVER VOLUME NAME local myapp_db_data # ボリュームも含めてすべて削除する場合は -v オプションを付ける(注意:データが消える) $ docker compose down -v [+] Running 2/2 ✔ Container myapp-db-1 Removed ✔ Volume myapp_db_data Removed
これは意図的な設計で、DBデータを誤って消さないための安全策です。
本番環境では down -v を実行しないようにチームで運用ルールを決めておきましょう。
tmpfsマウント(揮発性メモリの活用)
ボリュームやバインドマウントとは別に、tmpfsマウントという選択肢もあります。これはホストのメモリ(RAM)をコンテナの一時ファイル置き場として使う仕組みで、ディスクには何も書き込みません。
セッション情報や一時キャッシュなど、「コンテナが生きている間だけ必要なデータ」に使います。
# tmpfsマウントでコンテナの/tmpをRAMに割り当てる $ docker run -d \ --name redis_cache \ --tmpfs /tmp:size=256m \ redis:7
トラブルシュート|ボリュームとマウントのよくある問題
「Permission denied」でコンテナがデータを書き込めない
バインドマウントしたホストディレクトリにコンテナ内のプロセスが書き込めないケースです。原因はホスト側のファイル所有者・パーミッションとコンテナ内ユーザーの不一致です。
# コンテナログで Permission denied を確認する $ docker logs nginx_demo 2026/07/01 00:10:00 [warn] 1#1: could not build optimal types_hash... open() "/usr/share/nginx/html/index.html" failed (13: Permission denied) # ホスト側ディレクトリの所有者を確認する $ ls -la ~/nginx-demo/html/ drwxr-xr-x 2 root root 4096 Jul 1 09:00 . # コンテナ内のnginxプロセスが動くユーザーIDを確認する(nginx:1.26はUID=101) # ホスト側の所有者を合わせるか、その他ユーザー(o)に書き込み権を付ける $ chmod o+r ~/nginx-demo/html/ # または chown 101:101 ~/nginx-demo/html/ で所有者を合わせる
ボリュームが増殖して /var/lib/docker が枯渇する
使われていない古いボリュームが積み重なり、ホストのディスクを圧迫するケースです。# どのコンテナにも使われていない(dangling)ボリュームを確認する $ docker volume ls -f dangling=true DRIVER VOLUME NAME local old_project_db_data local test_vol_20260501 # 未使用ボリュームをまとめて削除する(注意:削除前に必ず内容を確認すること) $ docker volume prune WARNING! This will remove anonymous local volumes not used by at least one container. Are you sure you want to continue? [y/N] y Deleted Volumes: old_project_db_data test_vol_20260501 Total reclaimed space: 2.34 GB
Composeで「Volume not found」エラーが出る
compose.yml の services.xxx.volumes で参照しているボリューム名が、トップレベルの volumes セクションに宣言されていない場合に発生します。# NG: トップレベルのvolumesに宣言がない services: db: volumes: - db_data:/var/lib/mysql # ← エラー: db_data が宣言されていない # volumes: セクションがない # OK: トップレベルのvolumesで宣言する services: db: volumes: - db_data:/var/lib/mysql volumes: db_data: # ← ここに宣言が必要
本記事のまとめ
Dockerのデータ永続化は、ボリュームとバインドマウントを用途に合わせて使い分けるのが基本です。本番DBのデータは名前付きボリューム、開発用ソースコードはバインドマウント、という役割分担を覚えておきましょう。
| やりたいこと | コマンド・設定 |
|---|---|
| 名前付きボリュームを作成する | docker volume create ボリューム名 |
| ボリューム一覧を表示する | docker volume ls |
| ボリュームをコンテナにマウントして起動する | docker run -v ボリューム名:コンテナ内パス イメージ名 |
| バインドマウントでホストディレクトリを共有する | docker run -v /ホストパス:/コンテナ内パス イメージ名 |
| ボリュームの詳細を確認する | docker volume inspect ボリューム名 |
| 未使用ボリュームを一括削除する | docker volume prune |
| ComposeでDBデータを永続化する | compose.yml の volumes セクションに名前付きボリュームを宣言する |
| Compose停止時にボリュームを残す | docker compose down(-vオプションなし)を使う |
次のステップとして、バックアップ設計(ボリュームの定期的なtar保存)や、本番環境でのNFSボリューム・クラウドストレージとの連携にも挑戦してみてください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、本記事で紹介したDockerのボリューム設計を、さらに深く学べる講座を用意しています。
→ Dockerマスター講座の詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 次のページへ:Dockerのネットワーク設計入門|コンテナ間通信とbridge・host・noneの使い分け
- 前のページへ:DockerfileのマルチステージビルドとCompose設計|本番イメージの軽量化・セキュリティ・環境分離の実践手順
- この記事の属するカテゴリ:Dockerへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます