外观
数据与卷: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-datatext
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 -5text
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 idtext
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 myappuid 在不同镜像里不一样
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-nfsbash
docker run -d -v app-nfs:/app/data myapp:1.0.0NFS 不适合跑数据库
NFS 的延迟和锁语义与本地盘差异大,MySQL/PostgreSQL 跑在上面会出现"偶发卡顿""表损坏"。数据库用本地盘 + 主从,或云厂商的块存储。
七、查看卷占用了多少
bash
docker system df -vtext
VOLUME NAME LINKS SIZE
mysql-data 1 1.84GB
redis-data 1 42.1MB
app-data 0 512MBLINKS 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 |