Docker 存储
1. 三类挂载
| 类型 | 数据位置/管理者 | 典型用途 | 可移植性 |
|---|---|---|---|
| Named volume | Docker 管理 | 数据库、队列、应用持久数据 | 较好 |
| Bind mount | 明确的宿主机路径 | 源码、受控配置、宿主设备文件 | 依赖宿主路径 |
| tmpfs | 宿主内存 | 临时文件、短期敏感数据 | 不持久 |
容器可写层不适合持久数据:它随容器删除,写放大会影响性能,也难独立备份。
2. --mount 与 -v
推荐生产脚本使用可读性更强的 --mount:
docker run --rm \
--mount type=volume,src=mount-demo,dst=/data \
alpine:3.23 sh -c 'printf "hello\n" > /data/message.txt'用短语法挂载同一卷并读取上一步写入的文件,预期为 hello:
docker run --rm -v mount-demo:/data alpine:3.23 cat /data/message.txt这组例子只验证挂载和持久文件;无需数据库初始化。练习卷名为 mount-demo,结束后确认无其他用途再删除。数据库目录与凭据须按镜像版本另行准备。
Bind mount:
docker run --rm \
--mount type=bind,src="$PWD/config",dst=/app/config,readonly \
team-api:latest-v 在某些情况下会自动创建不存在的宿主目录,而 --mount 更倾向于明确报错;生产配置应预先创建并验证路径。
3. Named volume
docker volume create \
--label com.example.owner=platform \
app-data
docker volume ls
docker volume inspect app-data挂载:
docker run -d --name app \
--mount type=volume,src=app-data,dst=/var/lib/app \
team-app:latest只读挂载:
docker run --rm \
--mount type=volume,src=app-data,dst=/data,readonly \
alpine ls -la /data卷首次挂载到镜像中已有文件的非空目录时,Docker 的复制/遮蔽行为容易让人误判。关键目录应使用明确的初始化流程,并在镜像升级前测试。
4. Bind mount
sudo install -d -m 0750 -o 10001 -g 10001 /srv/team-api/data
docker run -d --name api \
--mount type=bind,src=/srv/team-api/data,dst=/app/data \
team-api:latest风险:
- 容器能直接修改宿主文件;
- 路径、SELinux label 和 UID/GID 与宿主耦合;
- 挂载到非空容器目录会遮蔽镜像原内容;
-v /:/host等高风险挂载可突破隔离。
配置文件优先只读:
docker run --rm \
--mount type=bind,src=/etc/team/app.yaml,dst=/app/config.yaml,readonly \
team-api:latestSELinux 主机可能需要 :z 或 :Z relabel;这会改变宿主文件标签,必须按照平台安全规范使用。
5. tmpfs
docker run --rm \
--mount type=tmpfs,dst=/run/app,tmpfs-size=67108864,tmpfs-mode=1770 \
alpine mount或:
docker run --rm --tmpfs /tmp:rw,noexec,nosuid,size=64m alpinetmpfs 数据在容器停止/宿主重启后消失,并占用内存。仍需设置大小并纳入内存预算。
6. 权限与 UID/GID
容器内 UID 是数字身份。没有 user namespace 时,容器 UID 10001 对宿主 bind mount 也是 10001。
检查镜像用户:
docker image inspect team-api:latest --format '{{.Config.User}}'检查实际进程:
docker exec api id
docker exec api stat -c '%u:%g %a %n' /app/data
sudo stat -c '%u:%g %a %n' /srv/team-api/data应在部署前创建正确所有者目录,不要用 chmod -R 777。
sudo chown -R 10001:10001 /srv/team-api/data
sudo chmod -R u=rwX,g=rX,o= /srv/team-api/data递归改权限可能昂贵且误伤已有数据;先确认路径和数据规模。
7. 只读根文件系统
docker run -d --name api \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
--mount type=volume,src=api-data,dst=/app/data \
team-api:latest这迫使应用明确区分:
- 持久数据;
- 临时可写目录;
- 只读程序与配置。
在测试环境收集所有写路径后再上线。
8. 查看容器挂载
docker inspect app --format '{{json .Mounts}}'
docker inspect app --format \
'{{range .Mounts}}{{println .Type .Source "->" .Destination "rw=" .RW}}{{end}}'查看卷被哪些容器引用:
docker ps -a \
--filter volume=app-data \
--format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'9. 卷备份
应用一致性优先于“打包目录”。数据库应首选数据库原生逻辑备份;文件卷要先停止写入或创建应用一致快照。
9.1 tar 备份示例
mkdir -p ./backup
docker run --rm \
--mount type=volume,src=app-data,dst=/source,readonly \
--mount type=bind,src="$PWD/backup",dst=/backup \
alpine:3.23 \
tar -C /source -czf /backup/app-data-2026-08-12.tar.gz .
sha256sum ./backup/app-data-2026-08-12.tar.gz \
> ./backup/app-data-2026-08-12.tar.gz.sha256确保 backup 目录存在,否则旧式 -v 语法可能创建意外目录。
9.2 恢复到新卷
docker volume create app-data-restore
docker run --rm \
--mount type=volume,src=app-data-restore,dst=/target \
--mount type=bind,src="$PWD/backup",dst=/backup,readonly \
alpine:3.23 \
tar -C /target -xzf /backup/app-data-2026-08-12.tar.gz验证:
docker run --rm \
--mount type=volume,src=app-data-restore,dst=/data,readonly \
alpine:3.23 \
find /data -maxdepth 2 -type f | head恢复演练必须包含应用启动和业务校验,不能只确认 tar 可解压。
10. 数据库备份
PostgreSQL 逻辑备份示例:
docker exec postgres \
pg_dump -U appuser -Fc appdb \
> ./backup/appdb-2026-08-12.dumpMySQL 示例:
docker exec mysql \
mysqldump --single-transaction -u root -p appdb \
> ./backup/appdb-2026-08-12.sql命令行密码可能泄露到历史或进程列表。使用受控凭据文件或 Secret,且按照实际数据库版本制定备份、加密和恢复流程。
11. 删除卷
docker volume rm unused-volume
docker volume prune危险: 卷可能包含唯一业务数据,且“当前没有容器引用”不代表不再需要。执行 prune 前必须导出卷清单、确认所有者、备份并完成恢复验证。
Compose:
docker compose down不会删除命名卷。以下命令会删除 Compose 管理的卷:
docker compose down --volumes危险: 不要在含数据库的项目中把
down -v当作普通重启命令。
12. 存储驱动与 image store
查看:
docker info --format 'driver={{.Driver}}'
docker info | sed -n '/Storage Driver/,+12p'
docker system df -vEngine 29 新安装默认使用 containerd image store。升级而来的机器可能保持旧存储状态。不要通过手工移动 /var/lib/docker 中的内部目录切换存储驱动;使用官方迁移步骤、维护窗口和完整备份。
13. 磁盘用量排查
df -hT
df -i
sudo du -xhd1 /var/lib/docker | sort -h
docker system df -v
docker ps -a --size重点检查:
- 未轮转的容器日志;
- 构建缓存;
- 无引用镜像;
- 已停止容器的可写层;
- 孤立卷;
- inode 耗尽;
- 应用误写容器层。
不要直接删除 /var/lib/docker/overlay2、containerd 内容或容器日志文件。先识别对象,再使用 Docker API 或正规日志轮转。
14. Volume driver
第三方 volume driver 可接入 NFS、云盘或存储系统。采用前确认:
- driver 维护和安全状态;
- 节点故障后的重新挂载行为;
- 并发读写语义;
- snapshot/backup 与加密;
- 超时对容器启动和停止的影响;
- driver 插件本身的宿主权限。
NFS 示例仅作语法参考:
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.0.2.10,rw,nfsvers=4 \
--opt device=:/exports/app \
app-nfs文档示例地址不可直接用于生产。
15. 常见故障
| 现象 | 常见原因 | 检查 |
|---|---|---|
| Permission denied | UID/GID、mode、SELinux/AppArmor | id、stat、审计日志 |
| 文件“消失” | 挂载遮蔽镜像目录 | docker inspect |
| 数据重建后丢失 | 写入了容器可写层 | docker diff、Mounts |
| no space left | block 或 inode 满、日志/缓存膨胀 | df -h、df -i、system df |
| device busy | 容器/进程仍引用挂载 | docker ps --filter volume=... |
| 数据损坏 | 非一致备份、强制终止、存储故障 | 应用日志、fs/db 工具 |
16. 官方参考
继续阅读
- 命令
- 命令
- 故障排查