Skip to content

Docker 存储 ​

1. 三类挂载 ​

类型数据位置/管理者典型用途可移植性
Named volumeDocker 管理数据库、队列、应用持久数据较好
Bind mount明确的宿主机路径源码、受控配置、宿主设备文件依赖宿主路径
tmpfs宿主内存临时文件、短期敏感数据不持久

容器可写层不适合持久数据:它随容器删除,写放大会影响性能,也难独立备份。

2. --mount 与 -v ​

推荐生产脚本使用可读性更强的 --mount:

bash
docker run --rm \
  --mount type=volume,src=mount-demo,dst=/data \
  alpine:3.23 sh -c 'printf "hello\n" > /data/message.txt'

用短语法挂载同一卷并读取上一步写入的文件,预期为 hello:

bash
docker run --rm -v mount-demo:/data alpine:3.23 cat /data/message.txt

这组例子只验证挂载和持久文件;无需数据库初始化。练习卷名为 mount-demo,结束后确认无其他用途再删除。数据库目录与凭据须按镜像版本另行准备。

Bind mount:

bash
docker run --rm \
  --mount type=bind,src="$PWD/config",dst=/app/config,readonly \
  team-api:latest

-v 在某些情况下会自动创建不存在的宿主目录,而 --mount 更倾向于明确报错;生产配置应预先创建并验证路径。

3. Named volume ​

bash
docker volume create \
  --label com.example.owner=platform \
  app-data

docker volume ls
docker volume inspect app-data

挂载:

bash
docker run -d --name app \
  --mount type=volume,src=app-data,dst=/var/lib/app \
  team-app:latest

只读挂载:

bash
docker run --rm \
  --mount type=volume,src=app-data,dst=/data,readonly \
  alpine ls -la /data

卷首次挂载到镜像中已有文件的非空目录时,Docker 的复制/遮蔽行为容易让人误判。关键目录应使用明确的初始化流程,并在镜像升级前测试。

4. Bind mount ​

bash
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 等高风险挂载可突破隔离。

配置文件优先只读:

bash
docker run --rm \
  --mount type=bind,src=/etc/team/app.yaml,dst=/app/config.yaml,readonly \
  team-api:latest

SELinux 主机可能需要 :z 或 :Z relabel;这会改变宿主文件标签,必须按照平台安全规范使用。

5. tmpfs ​

bash
docker run --rm \
  --mount type=tmpfs,dst=/run/app,tmpfs-size=67108864,tmpfs-mode=1770 \
  alpine mount

或:

bash
docker run --rm --tmpfs /tmp:rw,noexec,nosuid,size=64m alpine

tmpfs 数据在容器停止/宿主重启后消失,并占用内存。仍需设置大小并纳入内存预算。

6. 权限与 UID/GID ​

容器内 UID 是数字身份。没有 user namespace 时,容器 UID 10001 对宿主 bind mount 也是 10001。

检查镜像用户:

bash
docker image inspect team-api:latest --format '{{.Config.User}}'

检查实际进程:

bash
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。

bash
sudo chown -R 10001:10001 /srv/team-api/data
sudo chmod -R u=rwX,g=rX,o= /srv/team-api/data

递归改权限可能昂贵且误伤已有数据;先确认路径和数据规模。

7. 只读根文件系统 ​

bash
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. 查看容器挂载 ​

bash
docker inspect app --format '{{json .Mounts}}'
docker inspect app --format \
  '{{range .Mounts}}{{println .Type .Source "->" .Destination "rw=" .RW}}{{end}}'

查看卷被哪些容器引用:

bash
docker ps -a \
  --filter volume=app-data \
  --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'

9. 卷备份 ​

应用一致性优先于“打包目录”。数据库应首选数据库原生逻辑备份;文件卷要先停止写入或创建应用一致快照。

9.1 tar 备份示例 ​

bash
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 恢复到新卷 ​

bash
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

验证:

bash
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 逻辑备份示例:

bash
docker exec postgres \
  pg_dump -U appuser -Fc appdb \
  > ./backup/appdb-2026-08-12.dump

MySQL 示例:

bash
docker exec mysql \
  mysqldump --single-transaction -u root -p appdb \
  > ./backup/appdb-2026-08-12.sql

命令行密码可能泄露到历史或进程列表。使用受控凭据文件或 Secret,且按照实际数据库版本制定备份、加密和恢复流程。

11. 删除卷 ​

bash
docker volume rm unused-volume
docker volume prune

危险: 卷可能包含唯一业务数据,且“当前没有容器引用”不代表不再需要。执行 prune 前必须导出卷清单、确认所有者、备份并完成恢复验证。

Compose:

bash
docker compose down

不会删除命名卷。以下命令会删除 Compose 管理的卷:

bash
docker compose down --volumes

危险: 不要在含数据库的项目中把 down -v 当作普通重启命令。

12. 存储驱动与 image store ​

查看:

bash
docker info --format 'driver={{.Driver}}'
docker info | sed -n '/Storage Driver/,+12p'
docker system df -v

Engine 29 新安装默认使用 containerd image store。升级而来的机器可能保持旧存储状态。不要通过手工移动 /var/lib/docker 中的内部目录切换存储驱动;使用官方迁移步骤、维护窗口和完整备份。

13. 磁盘用量排查 ​

bash
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 示例仅作语法参考:

bash
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 deniedUID/GID、mode、SELinux/AppArmorid、stat、审计日志
文件“消失”挂载遮蔽镜像目录docker inspect
数据重建后丢失写入了容器可写层docker diff、Mounts
no space leftblock 或 inode 满、日志/缓存膨胀df -h、df -i、system df
device busy容器/进程仍引用挂载docker ps --filter volume=...
数据损坏非一致备份、强制终止、存储故障应用日志、fs/db 工具

16. 官方参考 ​

继续阅读