数据库和应用状态写入 volume,只读配置使用 bind mount,密钥通过文件挂载给指定服务。配置完成后还要核对进程 UID/GID,并分别执行容器重建和独立恢复。

容器把数据库状态写入 named volume,只读读取宿主配置,并从 secrets 文件读取密码的结构图
数据库写入 named volume,配置只读挂载,密钥仅挂载到需要它的服务。

根据数据用途选择 named volume、bind mount 或 tmpfs

类型适合内容优点主要风险
named volume数据库、队列、应用持久状态生命周期由 Docker 管理,路径不与单台主机布局强耦合宿主备份和迁移要明确卷名与驱动
bind mountCaddyfile、明确的数据目录、宿主脚本路径可见,便于现有备份与权限工具管理强依赖宿主路径、属主和 SELinux/AppArmor 条件
tmpfs临时敏感文件、无需持久化的高速临时数据不写入持久磁盘重启即丢失,容量占用内存

Docker storage 文档(在新标签页打开)建议优先用 volume 保存容器生成的持久数据;bind mount 更适合需要直接共享和管理的宿主文件。镜像层不适合承载运行数据,容器删除时可写层也会消失。

Compose 中把状态、配置和秘密分开

services:
  app:
    image: example/app:1.4.2
    environment:
      APP_CONFIG: /etc/example/app.yaml
      DB_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - app_data:/var/lib/example
      - ./config/app.yaml:/etc/example/app.yaml:ro
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password

volumes:
  app_data:

配置文件只读挂载,运行状态写入 named volume,数据库密码只授权给需要它的服务。不要把整个项目目录挂进生产容器,也不要把宿主 Docker socket 当作普通便利工具挂载;能访问 socket 的进程通常可以取得接近宿主 root 的控制能力。

用 inspect 确认实际挂载来源

docker inspect app \
  --format '{{range .Mounts}}{{printf "%s\t%s\t%s\t%s\n" .Type .Source .Destination .Mode}}{{end}}'

检查以下内容:

  • 数据目录的 Source 是预期卷或宿主路径;
  • 配置和密钥目的路径正确;
  • 应只读的挂载带 ro
  • 没有意外匿名卷覆盖镜像中的目录;
  • 备份程序访问的是同一个来源。

镜像声明 VOLUME 或 Compose 路径写错时,Docker 可能创建匿名卷。应用能运行,但运维人员备份了另一个空目录,故障时才会暴露问题。

权限按运行 UID 和 GID 配置

先查看镜像实际运行身份和目录:

docker inspect app --format 'user={{.Config.User}}'
docker compose exec app id
docker compose exec app sh -c 'namei -l /var/lib/example && touch /var/lib/example/.write-test'

数字 UID/GID 才是挂载权限判断依据,容器内用户名与宿主机同名并不代表身份相同。bind mount 可以在启动前由宿主设置属主:

sudo install -d -o 10001 -g 10001 -m 0750 /srv/example/data

named volume 的初始权限应由镜像入口或一次性初始化任务设置。不要让应用每次启动都递归 chown 大型数据目录;这会拖慢启动,也可能改坏共享数据。

Compose secrets 的挂载与读取

Docker Compose secrets 文档(在新标签页打开)要求服务显式授权后,才把对应 secret 暴露到 /run/secrets/<name>。应用优先支持 _FILE 配置,避免在命令行和普通环境变量中传递秘密。

docker compose exec app sh -c '
  test -r /run/secrets/db_password
  stat -c "%a %u:%g %n" /run/secrets/db_password
'

Compose 读取宿主机上的 secret 文件,再把它挂载到获准服务。它不会加密宿主文件,也不会自动轮换密钥或记录访问;需要这些功能时,应接入 Vault、云 KMS/Secret Manager 或同类系统。

秘密文件至少应满足:

  • 不进入 Git、镜像、构建参数或公开制品;
  • 宿主目录仅允许受管管理员读取;
  • 只授权给实际消费该秘密的服务;
  • 日志、错误响应和健康检查不输出秘密;
  • 轮换时有明确的重新加载和失效步骤。

数据库备份要使用一致性方法

直接打包运行中数据库的数据目录,可能得到无法恢复或时间点不一致的文件。PostgreSQL、MySQL 等数据库优先使用原生逻辑备份、物理备份或受支持的存储快照流程。

PostgreSQL 的 pg_dump(在新标签页打开) 可以在数据库仍被使用时生成一致性导出。以自定义格式备份为例:

docker compose exec -T postgres \
  pg_dump -U app -d app --format=custom \
  > backup.dump

备份完成后,在独立数据库执行恢复;pg_restore --exit-on-error(在新标签页打开) 会在首个 SQL 错误处停止:

pg_restore --list backup.dump >/dev/null
pg_restore --clean --if-exists --no-owner --exit-on-error \
  --dbname=postgresql://restore_user@restore-host/restore_db \
  backup.dump

再由应用读取关键表、执行登录或核心查询,并记录备份时间、数据库版本、文件大小和校验和。恢复测试不应覆盖当前生产卷。

重建容器后读取原数据

记录挂载来源、写入测试数据、重建容器、读取数据、备份并在独立环境恢复的验证流程
重建验证持久化,独立恢复验证备份;两者不能互相替代。

写入一个可识别文件,强制重建容器后再读取:

docker compose exec app sh -c 'printf persistence-probe > /var/lib/example/probe.txt'
docker compose up -d --force-recreate app
docker compose exec app cat /var/lib/example/probe.txt

迁移前停止写入或使用应用支持的一致性备份方式,记录 volume driver、UID/GID、目的路径和应用版本。迁移后先恢复数据和权限,再启动应用;不要让空数据库自动初始化后才覆盖文件。

docker compose down 默认不会删除 named volume,docker compose down -v 会删除声明卷和匿名卷。生产操作脚本应避免把 -v 放进普通停机或发布路径。

常见问题

Docker named volume 和 bind mount 怎么选?

容器生成的持久状态通常用 named volume;需要宿主直接管理的明确文件和目录用 bind mount。以数据所有者和备份方式决定。

Permission denied 可以直接 chmod 777 吗?

不应。先核对运行 UID/GID、挂载源属主、父目录执行权限、只读标志和安全模块策略,再授予最小权限。

Docker Compose secrets 会加密密钥吗?

普通 Compose secrets 负责把宿主 secret 文件交给获准服务,不提供集中加密、自动轮换和访问审计。

备份 Docker volume 目录就能恢复数据库吗?

不一定。运行中数据库需要一致性备份方式。使用数据库原生工具或受支持快照,并在独立环境完成恢复验证。

怎样确认容器重建后数据不会丢?

重建后能读到原数据,只能证明挂载仍然有效。备份还要在独立环境恢复成功,才能用于灾难恢复。