Skip to content

数据与卷:volume、bind mount 与权限 ​

容器删了数据就没了——除非你挂了卷。但挂错类型、搞错 UID,会遇到"宿主机上看得到、容器里写不进去"。

场景 ​

  • 数据库数据必须持久化。
  • 配置文件要能从宿主机改。
  • 多个容器共享同一份数据。
  • 卷要备份、要迁移到新机器。

一、三种挂载 ​

类型写法存储位置适用
named volume-v mydata:/var/lib/mysql/data/docker/volumes/mydata/_data数据库、需要 Docker 管理
bind mount-v /data/mysql:/var/lib/mysql你指定的宿主路径配置文件、代码、日志
tmpfs--tmpfs /tmp内存临时文件、敏感数据

推荐:数据用 named volume,配置用 bind mount。

bash
docker run -d --name db \
  -v mysql-data:/var/lib/mysql \
  -v /data/mysql-conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro \
  --tmpfs /tmp:size=64m \
  mysql:8.0

--mount 写法(更明确,推荐给脚本用):

bash
docker run -d --name db \
  --mount type=volume,source=mysql-data,target=/var/lib/mysql \
  --mount type=bind,source=/data/mysql-conf/my.cnf,target=/etc/mysql/conf.d/my.cnf,readonly \
  mysql:8.0
差异-v--mount
宿主路径不存在自动创建空目录报错
语法紧凑明确
适合手敲脚本/Compose

-v 自动创建空目录是常见坑:路径打错(/data/mysq)也会"成功"启动,然后数据写到一个新目录里,你还以为挂对了。

二、named volume 操作 ​

bash
docker volume create app-data
docker volume ls
docker volume inspect app-data
text
DRIVER    VOLUME NAME
local     app-data
local     mysql-data

[
    {
        "CreatedAt": "2026-10-27T21:02:11+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/data/docker/volumes/app-data/_data",
        "Name": "app-data",
        "Options": null,
        "Scope": "local"
    }
]

Mountpoint 是它在宿主机上的真实路径。可以直接 ls 看内容(需要 root)。

删除:

bash
docker volume rm app-data          # 只能删没被容器使用的
docker volume prune                # 删所有未使用的,会问确认

docker volume prune 会删数据库

它删的是"当前没有容器挂载"的卷。如果你先 docker rm 了容器再 prune,数据库文件就没了。生产机上执行前先 docker volume ls 逐个确认。

三、bind mount 与权限(最常见的坑) ​

现象:容器启动报 Permission denied,或者 Can't create/write to file。

bash
mkdir -p /data/mysql
docker run -d --name db -v /data/mysql:/var/lib/mysql mysql:8.0
docker logs db | tail -5
text
mysqld: Can't create/write to file '/var/lib/mysql/...' (Errcode: 13 - Permission denied)

原因:mysql 镜像里的进程以 uid 999 运行,而 /data/mysql 属主是 root、权限 755,uid 999 没有写权限。

bash
ls -ld /data/mysql
docker run --rm mysql:8.0 id
text
drwxr-xr-x 2 root root 4096 Oct 27 21:05 /data/mysql

uid=999(mysql) gid=999(mysql) groups=999(mysql)

三种解法:

bash
# 1. 把宿主目录给容器内的 uid(推荐)
chown -R 999:999 /data/mysql

# 2. 用命名卷,Docker 会把属主设成镜像声明的用户
docker run -d -v mysql-data:/var/lib/mysql mysql:8.0

# 3. 指定运行用户(要确保该用户对目录有权限)
docker run -d --user $(id -u):$(id -g) -v /data/app:/app myapp

uid 在不同镜像里不一样

nginx 是 101、mysql 是 999、postgres 是 999、redis 是 999、node 是 1000。不要猜,用 docker run --rm <image> id 查。

只读挂载 ​

bash
-v /data/config:/app/config:ro

配置文件、证书、静态资源都该用 ro。容器被攻破也改不了宿主机文件。

四、日志与数据分离 ​

bash
docker run -d --name app \
  -v /data/app/logs:/app/logs \
  -v app-data:/app/data \
  myapp:1.0.0
  • 日志用 bind mount:方便宿主机上的采集器(filebeat、logtail)读。
  • 业务数据用 named volume:Docker 管理,迁移时用 docker cp 或备份脚本。

五、备份与恢复 ​

named volume 备份 ​

bash
docker run --rm \
  -v mysql-data:/data \
  -v "$(pwd)":/backup \
  alpine tar czf /backup/mysql-data-$(date +%F-%H%M).tar.gz -C /data .
text
tar: removing leading '/' from member names

生成的 mysql-data-2026-10-27-2108.tar.gz 就是完整备份。

恢复 ​

bash
docker run --rm \
  -v mysql-data:/data \
  -v "$(pwd)":/backup \
  alpine sh -c 'rm -rf /data/* && tar xzf /backup/mysql-data-2026-10-27-2108.tar.gz -C /data'

恢复前先停容器

MySQL 运行时直接覆盖 /var/lib/mysql 会导致数据文件与 redo log 不一致,起不来。必须:

bash
docker stop db
# 恢复
docker start db

数据库自己的备份更可靠 ​

对 MySQL 来说,mysqldump 比拷文件安全:

bash
docker exec db sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --all-databases' \
  | gzip > /backup/mysql-all-$(date +%F).sql.gz

--single-transaction 保证 InnoDB 表的一致性快照,不锁表。

跨机器迁移 ​

bash
# 源机器
docker run --rm -v app-data:/data -v "$(pwd)":/backup alpine \
  tar czf /backup/app-data.tar.gz -C /data .

scp app-data.tar.gz 10.0.0.99:/tmp/

# 目标机器
ssh 10.0.0.99
docker volume create app-data
docker run --rm -v app-data:/data -v /tmp:/backup alpine \
  tar xzf /backup/app-data.tar.gz -C /data

六、NFS 与多机共享 ​

单机上 named volume 没问题;多台机器要共享数据时:

bash
docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=10.0.0.200,nfsvers=4.1,rw \
  --opt device=:/export/appdata \
  app-nfs
bash
docker run -d -v app-nfs:/app/data myapp:1.0.0

NFS 不适合跑数据库

NFS 的延迟和锁语义与本地盘差异大,MySQL/PostgreSQL 跑在上面会出现"偶发卡顿""表损坏"。数据库用本地盘 + 主从,或云厂商的块存储。

七、查看卷占用了多少 ​

bash
docker system df -v
text
VOLUME NAME    LINKS     SIZE
mysql-data     1         1.84GB
redis-data     1         42.1MB
app-data       0         512MB

LINKS 0 表示没有容器在用,可以清理(但先确认里面没数据)。

手动看:

bash
du -sh /data/docker/volumes/*/_data | sort -h | tail -5

八、踩过的坑 ​

现象根因处置
Permission denied容器内 uid 与宿主目录属主不一致chown 成镜像里的 uid,或用 named volume
-v 路径打错了却启动成功-v 会自动建目录改用 --mount,路径错会报错
数据"没了",实际写到了别处挂载路径与镜像声明的数据目录不一致docker inspect 看 .Mounts
重启后数据回退没挂载,写在了容器可写层确认 docker inspect 里有对应 Mount
docker prune 后数据库空了--volumes 删了未使用卷别在数据机上执行
备份文件很小,恢复后数据不全备份时容器在写停容器或用数据库自带备份
NFS 挂载慢网络/锁数据库不要用 NFS
只读挂载后应用启动失败应用要写配置目录拆开:配置 ro,运行目录 rw