目录

Docker Volume 教程:使用 Volume 和 Bind Mount 持久化数据

容器内写入的文件存在容器的可写层中,容器一旦删除,数据也会随之消失。数据库、用户上传的文件这类需要保存的数据,必须放在容器外部。Docker 提供三种挂载方式:

类型数据存在哪里适用场景
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 会自动创建。

创建一张表,写入一条数据,然后删除容器,再用同一个 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 的官方镜像改为将数据存放在 /var/lib/postgresql/18/docker 这类按主版本区分的子目录中,以便日后使用 pg_upgrade 升级。新的做法是挂载上一层目录 /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 是 Volume 在 Docker 主机上的实际位置。在 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,挂载在 /var/lib/postgresql。
  • docker rm -f 删除容器后,匿名 Volume 仍然保留,占用 40 MB。很多人的 Docker 越用越大,原因之一就是这些被遗忘的匿名 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
修改前的网页
nginx 显示主机上 site/index.html 的内容

在主机上使用编辑器修改 site/index.html 的标题并保存,然后刷新浏览器:

修改后的网页
无需重新构建镜像,也无需重启容器,刷新即可看到修改结果

这是开发时最常见的用法:代码留在主机上,用你熟悉的编辑器修改,容器负责运行。

注意事项:

  • 主机路径必须写成绝对路径。$(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
  • 第一个命令:将 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",本章的命令都提供了三种写法的标签页。

Bind mount 的文件权限不对?

Linux 上容器以 root 写入的文件,在主机上也属于 root。使用 -u $(id -u):$(id -g) 让容器以你的身份运行。

更新于 2026年10月14日