本节摘要:Podman 与 Docker 的存储差异集中在三处:镜像存储位置(用户家目录 与 系统目录的两套世界)、卷的物理形态(命名卷按用户隔离)、绑定挂载的权限语义(SELinux 标签与 UID 映射双重检查)。本节用三个故障案例把机制讲透,最后给出卷与绑定挂载的选型决策表。
迁移用户最常提交的三类工单,先给现象再给答案:
故障一:挂载了配置文件,容器读得到但写不进,报 Permission denied——SELinux 标签问题。故障二:数据库容器把 bind mount 当数据目录,容器内进程(以非 root 用户运行)建不了文件——UID 映射错位。故障三:Docker 迁过来的脚本找不到镜像——两套存储目录互不相通(第 2.1 节与第 3.2 节讲过机制,这里是症状)。前两个是本节主角。
在启用了 SELinux 的发行版(RHEL、Fedora 系)上,bind mount 默认会被容器拒绝写入——不是权限位的问题,是标签的问题:宿主文件带着用户主目录的 SELinux 标签,容器进程带着容器专用标签(第 6.1 节展开机制),策略默认禁止后者读写前者。Podman 与 Docker 在此的分野在于:Podman 把解决方案做进了挂载语法:
# 用 :Z 重贴标签(私有挂载,该目录只给这个容器用) podman run --rm -v ./data:/data:Z alpine:3.19 \ sh -c 'echo ok > /data/a.txt && cat /data/a.txt' # ok # 用 :z 重贴共享标签(多个容器共享同目录时用) podman run --rm -v ./shared:/shared:z app-a:latest ... podman run --rm -v ./shared:/shared:z app-b:latest ...
一个冒号的大小写,语义差别值得背下来:大写 Z 重贴为"单容器专用"标签(container_file_t 加独有类别),小写 z 重贴为"共享"标签(container_file_t 通用类别)。选错方向各有代价:该用 Z 的场景用了 z,等于把目录开放给所有容器进程读写,隔离强度下降;该用 z 的场景用了 Z,第二个容器读不了。默认建议:私有优先,确需共享再降级到 z。另外注意:重贴标签修改的是宿主文件属性——对系统目录别乱用(把 /usr 贴成容器标签会让系统服务读不了它),仅对数据目录使用。
第 2.2 节建立的映射认知直接套用。典型场景:官方 PostgreSQL 镜像里的进程以容器内 UID 999(postgres 用户)运行,bind mount 的宿主目录属主是你(UID 1000)。容器内 999 落到宿主机是 100998,与 1000 无交集——写失败。三种修法按推荐顺序:
# 修法一(最常用):chown 到映射后的属主,再挂载 mkdir -p ~/pgdata podman unshare chown 999:999 ~/pgdata # unshare 内的 999 = 宿主机视角的 100998?不—— # unshare 内视角下 999 就是容器视角的 999, # 映射到宿主机是你的身份所在区间,此操作让容器内 # postgres 用户恰好拥有该目录 # 修法二:keep-id(容器内直接使用你的宿主 UID) podman run --userns=keep-id --rm -v ~/pgdata:/var/lib/postgresql/data:Z \ postgres:16-alpine ... # 适用:镜像允许以任意 UID 运行(镜像里有 entrypoint 适配) # 修法三:命名卷(让 Podman 管理属主) podman volume create pgdata podman run -v pgdata:/var/lib/postgresql/data:Z postgres:16-alpine ... # 命名卷由 Podman 初始化属主,官方镜配镜像的 volume 声明 # 时容器会按镜像要求自动 chown——最省心也最推荐
两种数据放置方式的对位比较,落在选型上:
| 维度 | 命名卷 | 绑定挂载 |
|---|---|---|
| 物理位置 | 用户级卷目录(rootless 在家目录下) | 宿主任意路径 |
| 属主初始化 | Podman 可按镜像声明自动处理 | 你自己负责 |
| SELinux | 挂载时默认处理妥当 | 需要显式 :Z 或 :z |
| 备份可见性 | 需通过 podman volume 定位路径 | 路径直观,rsync 直用 |
| 适合 | 数据库、应用状态等"容器私有数据" | 配置文件、源码、共享目录 |
| 与 Docker 语义 | 基本对齐(位置不同) | 对齐(但权限检查更严) |
经验法则一句话:数据归容器管的用卷,数据归你管的用 bind mount。数据库的数据文件属于前者(你不该直接翻它内部结构),nginx 的配置文件属于后者(你要用编辑器直接改它)。

把坑串成流程。背景:把一个 Docker 上跑了两年的 PostgreSQL 容器迁到 rootless Podman,数据必须无损。步骤:先用 Docker 侧工具导出或直接停库拷贝 bind mount 目录;目标侧 podman volume create pgdata 建卷;podman unshare chown 999:999 卷路径 或依赖镜像的自动初始化;启动时挂载 pgdata:/var/lib/postgresql/data:Z;恢复数据后用 pg 内置工具校验。结果:容器内 postgres 用户读写正常,数据完整。解读:整个流程的关键动作只有两步——把"数据的所有权"从宿主机用户视角翻译到容器视角(chown),把"数据的访问权"从用户域开放给容器域(:Z)。变式:MySQL、Redis 等官方镜像同理,先查镜像内进程的 UID,再走同一套翻译动作。