首页 / Docker 入门教程 / 数据持久化:Volumes 数据卷

Docker 入门教程

数据持久化:Volumes 数据卷

本教程共 25 篇 · 第 16 篇 · 更新于 2026-07-26 · 约 8 分钟阅读

DockerDocker 入门教程数据卷Volumes数据持久化docker volume容器存储

16. 数据持久化:Volumes 数据卷

本节目标:搞懂容器文件系统为什么存不住数据,学会用数据卷(Volumes)把数据持久化下来,让容器删了重建数据还在。

前面跑容器时你可能发现一个现象:在容器里写了文件,容器一删,文件就没了。这对临时任务没影响,但对数据库来说是灾难—每次重启都回到出厂状态。

这一章解决的就是「数据怎么活下来」的问题。

容器文件系统是临时的

先理解容器是怎么处理文件的。

容器启动时,它的文件系统由两部分拼成:镜像里的只读层,加上一层属于这个容器的「可写层」。容器里新建、修改、删除的文件,都写在这个可写层里。

关键在于:这个可写层跟着容器走,容器删了,可写层也跟着删了。 镜像本身不变,但你在容器里产生的数据没了。

来做个实验直观感受一下:

docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"

这条命令启动一个 ubuntu 容器,往 /data.txt 写一个随机数,然后挂着不退出。

进去看一眼:

docker exec -it <容器ID> cat /data.txt
# 输出一个随机数,比如 4273

再开一个同样镜像的容器:

docker run -it ubuntu ls /
# 列表里根本没有 data.txt

两个容器用的是同一个镜像,但文件互不可见。因为各自的可写层是独立的。

把第一个容器删掉,那个随机数就彻底消失了。这就是容器文件系统的「临时性」(ephemeral)。

数据卷:把数据挪到容器外面

数据卷(Volume)就是来解决这个问题的。它相当于在容器里开个口子,把某个目录「挂」到 Docker 管理的一块外部存储上。容器往这个目录写文件,实际写到的是外部存储,容器删了,外部存储还在。

打个比方:容器的可写层像白板,擦了就没了;数据卷像笔记本,写完能带走、能翻回去。

挂载一个具名卷(named volume):

docker run -d -p 80:80 -v log-data:/logs docker/welcome-to-docker

-v log-data:/logs 的意思是:创建一个叫 log-data 的数据卷,挂到容器的 /logs 目录。容器里往 /logs 写的文件,实际存在 log-data 卷里。

如果这个卷不存在,Docker 会自动创建。之后哪怕删掉这个容器,再用同一个卷启动新容器,数据还在:

docker rm -f <容器ID>
docker run -d -p 80:80 -v log-data:/logs docker/welcome-to-docker
Tip

同一个卷可以挂到多个容器上,实现容器间共享数据。比如日志收集场景:业务容器写日志到卷,另一个收集容器从同一个卷读日志。

具名卷 vs 匿名卷

数据卷分两种:

  • 具名卷(named volume):有名字,比如上面的 log-data。好管理,推荐用。
  • 匿名卷(anonymous volume):没名字,Docker 给一串随机哈希当 ID。删容器时容易成孤儿,不好管。

具名卷的挂载写法是 卷名:容器路径

docker run -v my-data:/app/data nginx

匿名卷的挂载写法是只有容器路径,不带卷名:

docker run -v /app/data nginx

第二种写法 Docker 会生成一个匿名卷,但你不知道它叫啥,后续清理很麻烦。除非临时用,否则别这么写。

管理数据卷的命令

数据卷有独立的生命周期,不跟容器绑定。容器删了卷还在,得单独管理。常用命令:

# 创建一个卷
docker volume create todo-db

# 列出所有卷
docker volume ls

# 查看某个卷的详情(包括实际存储路径)
docker volume inspect todo-db

# 删除一个卷(卷没被容器使用时才能删)
docker volume rm todo-db

# 删除所有未被使用的卷
docker volume prune

docker volume inspect 能看到卷的实际存储位置:

$ docker volume inspect todo-db
[
    {
        "CreatedAt": "2026-07-26T02:18:36Z",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Scope": "local"
    }
]

Mountpoint 就是数据在宿主机磁盘上的真实位置。Linux 上直接能访问这个目录(需要 root 权限);Docker Desktop 上数据存在 VM 里,得进 VM 才能看到。

Note

一般不需要直接操作 Mountpoint 目录,Docker 替你管着。除非要备份、迁移数据,才需要知道它在哪。

实战:给数据库持久化

最能体现数据卷价值的场景是数据库。以 PostgreSQL 为例,默认数据存在容器的 /var/lib/postgresql/data 目录,不挂卷的话容器一删数据全没。

用数据卷挂载:

docker volume create postgres_data
docker run --name db -e POSTGRES_PASSWORD=secret -d \
    -v postgres_data:/var/lib/postgresql/data \
    postgres:18

连进去写点数据:

docker exec -ti db psql -U postgres

在 psql 里建表插数据:

CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,
    description VARCHAR(100)
);
INSERT INTO tasks (description) VALUES ('Finish work'), ('Have fun');
SELECT * FROM tasks;
\q

现在把容器删了:

docker stop db
docker rm db

用同一个卷起个新容器:

docker run --name new-db -d -v postgres_data:/var/lib/postgresql/data postgres:18

查一下数据还在不在:

docker exec -ti new-db psql -U postgres -c "SELECT * FROM tasks"

数据原封不动。这就是数据卷的作用—容器是临时的,数据是持久的。

Warning

第二次起容器时我没传 POSTGRES_PASSWORD。这个环境变量只在首次初始化数据库时用,数据库已经存在了就不会再用它。如果你传了不同的密码,也不会改已存在的数据库密码,容易让人困惑。

—mount:更清晰的挂载语法

-v 是老写法,简洁但容易歧义。比如 -v log-data:/logs,光看命令分不清 log-data 是卷名还是宿主机路径。

--mount 用键值对显式声明,更清晰,官方推荐:

docker run -d --name my_site -p 8080:80 \
    --mount type=volume,source=log-data,target=/logs \
    nginx
  • type=volume 表示这是数据卷挂载(bind mount 是 type=bind)。
  • source 是卷名。
  • target 是容器内路径。

两种写法效果一样,--mount 更明确,-v 更简短。日常 -v 够用,复杂场景或生产环境建议 --mount,可读性好。

删除容器时连带删卷

默认删容器不会删卷,这是好事,防止误删数据。但如果你确定某个容器的卷是一次性的,可以用 -v 标志连带删除匿名卷:

docker rm -v <容器ID>

注意:-v 只删该容器创建的匿名卷,不会删具名卷。具名卷得用 docker volume rm 单独删,这是刻意的保护设计。

数据卷小结

把这一章的要点收一下:

  1. 容器可写层是临时的,容器删了数据就没了。
  2. 数据卷把数据挪到 Docker 管理的外部存储,容器删了卷还在。
  3. 优先用具名卷,别用匿名卷,好管理。
  4. 数据卷独立于容器,有 create/ls/inspect/rm/prune 一套命令。
  5. 同一个卷能挂多个容器,实现数据共享。
  6. 数据库类应用必挂卷,否则数据丢失。
Note

除了数据卷,Docker 还有「bind mount」(绑定挂载),它把宿主机的指定目录直接挂进容器,更适合开发场景。两者区别和用法是下一章的主题。

下一章讲 bind mount,看看它和数据卷有啥不一样,什么时候该用哪个。