目次

Docker Volume 入門:Volume と bind mount でデータを永続化

コンテナ内で書き込んだファイルは、コンテナの書き込み可能レイヤーに保存されます。コンテナを削除すると、データも一緒に消えてしまいます。データベースやユーザーがアップロードしたファイルなど、保存が必要なデータはコンテナの外に置く必要があります。Docker には次の3種類のマウント方法があります。

種類データの保存場所適した用途
VolumeDocker が管理する領域データベース、本番環境で永続化するデータ
Bind mountホスト上で指定したフォルダー開発中のコード同期、ホストの設定ファイルの読み込み
tmpfsメモリ一時データ、ディスクに書き込みたくないデータ

Volume:コンテナを削除してもデータは残る

PostgreSQL を使って実際に確認します。データベースを起動し、pgdata という名前の Volume にデータを保存します。

docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql postgres:18

-v pgdata:/var/lib/postgresql は、pgdata という名前の Volume をコンテナ内の /var/lib/postgresql にマウントするという意味です。Volume が存在しない場合、Docker が自動的に作成します。

テーブルを作成してデータを1件書き込み、コンテナを削除してから、同じ Volume を使って新しいコンテナを起動します。

$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql postgres:18
119ed37ba5c6dc12a773a32d4ef296523529552f94a2ab1ee56113c1b990d7ae

$ docker ps --filter name=db
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS         PORTS      NAMES
119ed37ba5c6   postgres:18   "docker-entrypoint.s…"   8 seconds ago   Up 8 seconds   5432/tcp   db

$ docker exec db psql -U postgres -c "CREATE TABLE notes (id serial, body text); INSERT INTO notes (body) VALUES ('hello volume');"
CREATE TABLE
INSERT 0 1

$ docker exec db psql -U postgres -c "SELECT * FROM notes;"
 id |     body
----+--------------
  1 | hello volume
(1 row)


$ docker rm -f db
db

$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql postgres:18
d94fb2966b3dc5790239b4a48d180aedd193f6fc2c846d6878a7c5fcc450f7cd

$ docker exec db psql -U postgres -c "SELECT * FROM notes;"
 id |     body
----+--------------
  1 | hello volume
(1 row)

新しいコンテナから、前のコンテナが書き込んだデータを読み取れました。データは Volume に保存されるため、コンテナのライフサイクルとは関係ありません。

PostgreSQL 18 のデータディレクトリ

古いチュートリアルでは -v pgdata:/var/lib/postgresql/data と書かれていることがよくあります。これは PostgreSQL 17 以前の書き方で、postgres:18 では起動に失敗します。

$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres:18
c1cec1074a4d440c1f6d106d994171d290cd8ea9da3266d798c0c81662bd8281

$ docker ps -a --filter name=db
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS                     PORTS     NAMES
c1cec1074a4d   postgres:18   "docker-entrypoint.s…"   8 seconds ago   Exited (1) 7 seconds ago             db

$ docker logs db
Error: in 18+, these Docker images are configured to store database data in a
       format which is compatible with "pg_ctlcluster" (specifically, using
       major-version-specific directory names).  This better reflects how
       PostgreSQL itself works, and how upgrades are to be performed.

       See also https://github.com/docker-library/postgres/pull/1259

       Counter to that, there appears to be PostgreSQL data in:
         /var/lib/postgresql/data (unused mount/volume)

       This is usually the result of upgrading the Docker image without
       upgrading the underlying database using "pg_upgrade" (which requires both
       versions).

       The suggested container configuration for 18+ is to place a single mount
       at /var/lib/postgresql which will then place PostgreSQL data in a
       subdirectory, allowing usage of "pg_upgrade --link" without mount point
       boundary issues.

       See https://github.com/docker-library/postgres/issues/37 for a (long)
       discussion around this process, and suggestions for how to do so.

$ docker rm -f db
db

$ docker volume rm pgdata
pgdata

PostgreSQL 18 の公式イメージでは、pg_upgrade で後からアップグレードできるように、データを /var/lib/postgresql/18/docker のような「メジャーバージョンごとに分かれた」サブディレクトリに保存するようになりました。新しい方法では、1つ上の階層である /var/lib/postgresql をマウントします。

$ docker volume ls
DRIVER    VOLUME NAME
local     5775c1d90defc3dc23a92cdde10cd62b182d9c774ae43f0a40595fe62131a63c
local     pgdata

$ docker volume inspect pgdata
[
    {
        "CreatedAt": "2026-10-13T00:09:44Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
        "Name": "pgdata",
        "Options": null,
        "Scope": "local"
    }
]

$ docker exec db sh -c 'echo $PGDATA; ls /var/lib/postgresql'
/var/lib/postgresql/18/docker
18
  • PGDATA は現在 /var/lib/postgresql/18/docker です。
  • docker volume inspect の Mountpoint は、Docker ホスト上にある Volume の実際の場所です。Docker Desktop では Docker の Linux 仮想マシン内にあるため、Windows / macOS のエクスプローラーから直接確認することはできません。バックアップには、以下で紹介する方法を使ってください。

Volume の管理

docker volume ls                 # すべての Volume を一覧表示
docker volume inspect pgdata     # 詳細情報を確認
docker volume rm pgdata          # 削除(コンテナで使用されていない場合のみ)
docker volume prune              # 未使用の匿名 Volume を削除
docker volume prune -a           # 名前付き Volume も削除(注意!)

docker volume prune -a と docker system prune --volumes を実行すると、データベースのデータが削除されます。実行前に確認するか、先にバックアップを作成してください。

匿名 Volume はどこから来るのか

-v を指定していなくても、多くのデータベースイメージは Volume を作成します。Dockerfile に VOLUME 命令があるためです。

$ docker volume ls
DRIVER    VOLUME NAME

$ docker run -d --name anon -e POSTGRES_PASSWORD=secret postgres:18
1db583d1b34d0482cfa458a5fe4a7fc7c1320633bde0339407a0c24fe62ad630

$ docker volume ls
DRIVER    VOLUME NAME
local     0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8

$ docker inspect --format '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Destination}}{{println}}{{end}}' anon
volume 0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8 -> /var/lib/postgresql

$ docker rm -f anon
anon

$ docker volume ls
DRIVER    VOLUME NAME
local     0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8

$ docker volume prune -f
Deleted Volumes:
0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8

Total reclaimed space: 40.49MB
  • docker run を実行しただけで、docker volume ls に長いランダムな名前の匿名 Volumeが1つ増え、/var/lib/postgresql にマウントされます。
  • docker rm -f でコンテナを削除しても、匿名 Volume は残ったままで、40 MB を占有します。Docker を使うほどディスク使用量が増えていく原因の1つは、忘れられた匿名 Volume です。
  • docker volume prune は、使われていない匿名 Volume を削除します。コンテナを削除するときに -v(docker rm -fv anon)を付けても、その匿名 Volume を一緒に削除できます。

用途がひと目でわかるよう、常に名前付き Volume を使うことをおすすめします。

Bind mount:ホストのフォルダーをコンテナにマウントする

docker run -d --name site -p 8080:80 -v "$(pwd)/site:/usr/share/nginx/html:ro" nginx:1.30
docker run -d --name site -p 8080:80 -v "${PWD}/site:/usr/share/nginx/html:ro" nginx:1.30
docker run -d --name site -p 8080:80 -v "%cd%/site:/usr/share/nginx/html:ro" nginx:1.30
変更前のWebページ
nginx がホスト上の site/index.html の内容を表示

ホスト上でエディターを使って site/index.html の見出しを変更して保存し、ブラウザーを再読み込みします。

変更後のWebページ
イメージの再ビルドもコンテナの再起動も不要で、再読み込みすると変更が反映されます

これは開発で最もよく使われる方法です。コードはホスト上に置き、使い慣れたエディターで編集し、コンテナで実行します。

注意点:

  • ホストのパスは絶対パスで指定します。$(pwd) は現在のディレクトリに置き換わります(Windows PowerShell では ${PWD} を使います)。
  • パスではなく名前を指定した場合(例:site:/data)、Docker は Volume 名として扱います。
  • Linux では、コンテナが root として書き込んだファイルは、ホスト上でも root の所有になります。-u $(id -u):$(id -g) を使うと、コンテナを自分のユーザーとして実行できます。
  • 読み取り専用でマウントするには :ro を付けます。

-v と --mount で存在しないパスを指定した場合

$ ls -d nothere
ls: nothere: No such file or directory

$ docker run --rm -v "$(pwd)/nothere:/data" alpine:3.23 ls -la /data
total 8
drwxr-xr-x    2 root     root          4096 Oct 13 00:09 .
drwxr-xr-x    1 root     root          4096 Oct 13 00:09 ..

$ ls -ld nothere
drwxr-xr-x    2 root     root          4096 Oct 13 00:09 nothere

$ docker run --rm --mount type=bind,src="$(pwd)/nothere2",dst=/data alpine:3.23 ls /data
docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /work/nothere2

Run 'docker run --help' for more information

Windows での書き方(タブをクリックして切り替えます):

docker run --rm -v "$(pwd)/nothere:/data" alpine:3.23 ls -la /data
docker run --rm --mount type=bind,src="$(pwd)/nothere2",dst=/data alpine:3.23 ls /data
docker run --rm -v "${PWD}/nothere:/data" alpine:3.23 ls -la /data
docker run --rm --mount type=bind,src="${PWD}/nothere2",dst=/data alpine:3.23 ls /data
docker run --rm -v "%cd%/nothere:/data" alpine:3.23 ls -la /data
docker run --rm --mount type=bind,src="%cd%/nothere2",dst=/data alpine:3.23 ls /data
  • -v では、存在しないホストパスを指定すると空のフォルダーが自動的に作成されます(しかも root の所有になります)。コンテナは起動しますが、マウントされるのは空のフォルダーです。「マウントしたのにファイルがない」と戸惑う原因になりがちです。
  • --mount type=bind では、存在しないパスを指定するとエラーになります。パスの入力ミスに気付きやすくなります。

--mount の書き方は長くなりますが、より明確です。

docker run -d \
  --mount type=volume,src=pgdata,dst=/var/lib/postgresql \
  --mount type=bind,src="$(pwd)/init.sql",dst=/docker-entrypoint-initdb.d/init.sql,readonly \
  -e POSTGRES_PASSWORD=secret postgres:18
docker run -d `
  --mount type=volume,src=pgdata,dst=/var/lib/postgresql `
  --mount type=bind,src="${PWD}/init.sql",dst=/docker-entrypoint-initdb.d/init.sql,readonly `
  -e POSTGRES_PASSWORD=secret postgres:18
docker run -d ^
  --mount type=volume,src=pgdata,dst=/var/lib/postgresql ^
  --mount type=bind,src="%cd%/init.sql",dst=/docker-entrypoint-initdb.d/init.sql,readonly ^
  -e POSTGRES_PASSWORD=secret postgres:18

tmpfs:メモリ上に保存する

$ docker run --rm --tmpfs /cache:size=64m alpine:3.23 df -h /cache
Filesystem                Size      Used Available Use% Mounted on
tmpfs                    64.0M         0     64.0M   0% /cache

データはメモリ上にのみ保存され、コンテナを停止すると消えます。一時ファイルに適しています(Linux コンテナのみ)。

Volume のバックアップと復元

Volume の場所は Docker が管理します。最も汎用的なバックアップ方法は、一時コンテナを起動して Volume を tar ファイルにまとめることです。

$ docker run --rm -v pgdata:/data:ro -v "$(pwd):/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .

$ ls -lh pgdata.tar.gz
-rw-r--r--    1 root     root        4.6M Oct 13 00:10 pgdata.tar.gz

$ docker volume create pgdata-restore
pgdata-restore

$ docker run --rm -v pgdata-restore:/data -v "$(pwd):/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data

$ docker run --rm -v pgdata-restore:/data alpine:3.23 ls /data/18/docker
PG_VERSION
base
global
pg_commit_ts
pg_dynshmem
… (以下省略)

$ rm pgdata.tar.gz

Windows での書き方(タブをクリックして切り替えます):

docker run --rm -v pgdata:/data:ro -v "$(pwd):/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
docker run --rm -v pgdata-restore:/data -v "$(pwd):/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
docker run --rm -v pgdata:/data:ro -v "${PWD}:/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
docker run --rm -v pgdata-restore:/data -v "${PWD}:/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
docker run --rm -v pgdata:/data:ro -v "%cd%:/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
docker run --rm -v pgdata-restore:/data -v "%cd%:/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
  • 1つ目のコマンドでは、pgdata を読み取り専用で /data に、現在のフォルダーを /backup にマウントし、tar で /data を pgdata.tar.gz にまとめてホストの現在のフォルダーに保存します。
  • 復元時は新しい Volume を作成し、同じ方法でその中に解凍します。復元した Volume では、18/docker の下に PostgreSQL のデータファイルがあることを確認できます。

稼働中のデータベースのファイルを直接アーカイブすると、データに不整合が生じることがあります。上の例では、データベースを停止してからバックアップしています。データベースには、pg_dump や mysqldump などの専用エクスポートツールを使うことをおすすめします。詳しくはDocker で PostgreSQL を実行するを参照してください。

どれを使えばよいですか?

  • データベースなど、長期保存が必要なデータ → Volume
  • 開発中にコードの変更をすぐ確認したい → Bind mount
  • ホストの設定ファイルをコンテナに読み込ませる → Bind mount(読み取り専用)
  • 一時データで、保存する必要がない → tmpfs またはコンテナ内に直接書き込む

まとめ

  • データベースなど、保存が必要なデータには名前付き Volume を使います。
  • 開発時は Bind mount を使い、コンテナからホスト上のコードを直接利用します。
  • -v は存在しないホストパスを空のフォルダーとして作成します。--mount はエラーを返します。
  • Volume をバックアップするには、一時コンテナに Volume とホストのフォルダーを同時にマウントし、tar でアーカイブします。

練習問題

練習 1:Volume notes を作成し、コンテナからファイルを書き込みます。コンテナを削除した後、別のコンテナからそのファイルを読み取ってください。

解答
docker volume create notes
docker run --rm -v notes:/data alpine:3.23 sh -c "echo hello > /data/a.txt"
docker run --rm -v notes:/data alpine:3.23 cat /data/a.txt

hello と出力されます。Volume の管理コマンドについては docker volume を参照してください。

練習 2:docker run -v data:/app と docker run -v ./data:/app の違いは何ですか?

解答

data にはスラッシュが含まれていないため、Volume 名として扱われます。./data はパスなので Bind mount となり、ホストの現在のフォルダー内にある data をマウントします。

よくある質問

Volume のデータはコンピューターのどこに保存されますか?

Linux では /var/lib/docker/volumes/名前/_data にあります。Docker Desktop の Volume は Linux 仮想マシン内にあるため、Windows のエクスプローラーや Finder からは見えません。Docker Desktop の Volumes 画面で確認できます。

Windows で -v のホストパスはどう書きますか?

PowerShell では "${PWD}/site:/app"、CMD では "%cd%/site:/app" を使います。この章のコマンドには、3種類の書き方を切り替えるタブがあります。

Bind mount のファイル権限がおかしい場合は?

Linux では、コンテナが root として書き込んだファイルは、ホスト上でも root の所有になります。-u $(id -u):$(id -g) を使うと、コンテナを自分のユーザーとして実行できます。

更新日 2026年10月14日