Podman 工程手册:从 Rootless 用户边界到可交接容器运行时
Podman 最值得团队采用的部分,不是把 docker 替换成 podman,而是把“谁拥有容器状态、谁能控制这些进程”从宿主 root 收回到明确用户。Podman 命令参考把 rootless 用户命名空间、默认 socket、连接优先级与用户存储写成公开语义。代价也在这里:普通用户、root 用户和其他账号拥有彼此不可见的容器存储;bind mount 需要同时解释宿主权限、容器 UID 和 SELinux 标签;依赖 Docker API 的工具只能获得兼容入口,不能获得行为完全等价的承诺。
在 Linux 上,Podman 可以直接使用宿主内核运行 rootless 容器。在 macOS 和 Windows 上,Linux 容器仍要进入由 podman machine 管理的虚拟机。Podman Machine 参考明确说明这些平台必须使用 Machine;“rootless”指的是虚拟机内的用户模型,不等于没有 VM、没有 socket 或没有持久磁盘。
先证明命令连到了谁
安装包来源、客户端版本、默认连接、rootless 状态和存储根是第一组交接证据:
podman version
podman system connection list
podman info --format 'rootless={{.Host.Security.Rootless}} graphRoot={{.Store.GraphRoot}} driver={{.Store.GraphDriverName}}'
podman ps -a
podman images --digests
podman volume lsLinux 团队基线若选择 rootless,rootless=true 应稳定成立,graphRoot 应落在当前用户可解释的数据目录。随后分别执行普通用户 podman images 与经批准的 sudo podman images,两边结果不同是用户边界生效,不是镜像丢失。不要复制内部 graph root 来“同步”;可交付镜像应进入受控 Registry,并以 Digest 复核。
macOS 或 Windows 还要保存 Machine 身份:
podman machine list
podman machine info
podman system connection list
podman info新建团队专用 Machine 时显式给出资源和 rootful 选择:
podman machine init --rootful=false --cpus 4 --memory 4096 --disk-size 40 dev-podman
podman machine start dev-podman
podman machine inspect dev-podmanCPU、内存、磁盘和 VM provider 都是开发机基线的一部分。切换 Machine 的 rootful/rootless 默认值会把连接指向另一套存储;对象通常只是暂时不可见,不应在没有盘点时执行 reset。
Rootless 的真实权限边界
Rootless Podman 会为当前用户创建用户命名空间,并使用 /etc/subuid、/etc/subgid 提供额外映射范围。先核对辅助程序和映射:
command -v podman newuidmap newgidmap
grep "^${USER}:" /etc/subuid /etc/subgid
podman unshare cat /proc/self/uid_map
podman info --debug映射范围必须存在、足够且不与团队制度冲突。管理员调整映射后,旧 pause process 可能继续持有原命名空间;先停止当前用户的容器,再执行 podman system migrate,不要手工删除 PID 或存储元数据。
Rootless 降低了常驻高权限 daemon 的暴露面,却不是强沙箱。容器仍共享宿主内核,宿主目录挂载、设备、capability、seccomp、网络和内核漏洞仍决定风险。陌生镜像先以只读文件系统、非 root 用户、最少 capability 和无宿主 socket 的方式验证,不能因为命令由普通用户发起就跳过镜像来源与运行参数审查。
用正反实验验证端口和用户映射
下面的夹具只操作固定名称容器:
set -eu
name=te05-podman-ok
podman rm -f "$name" >/dev/null 2>&1 || true
podman run -d --name "$name" -p 127.0.0.1:18085:8080 \
docker.io/library/nginxinc/nginx-unprivileged:alpine
podman inspect "$name" --format '{{.State.Status}} {{.HostConfig.PortBindings}}'
curl --fail --silent http://127.0.0.1:18085/ >/dev/null
podman top "$name" user huser
podman rm -f "$name"inspect 应显示运行中,HTTP 检查退出 0,top 应能解释容器用户到宿主用户命名空间的映射。团队夹具应固定经批准的镜像 Digest;这里的公开标签只适合演示命令形状。
反向实验用于暴露 bind mount 责任:
set -eu
fixture="$PWD/.te05-podman-mount"
case "$fixture" in "$PWD"/.te05-*) ;; *) exit 64;; esac
rm -rf -- "$fixture"
mkdir -p "$fixture"
printf 'seed\n' > "$fixture/input.txt"
set +e
podman run --rm -v "$fixture:/work" docker.io/library/alpine:3.22 \
sh -c 'printf "container\n" >> /work/input.txt'
rc=$?
set -e
printf 'write_rc=%s\n' "$rc"
ls -ln "$fixture/input.txt"
rm -rf -- "$fixture"SELinux enforcing 主机上,未标注挂载可能以非零退出;项目私有目录可评估 :Z,多个容器共享时评估 :z。二者都会改宿主标签,大目录重标记有成本,系统目录不应这样处理。UID 不匹配优先从镜像用户、--userns=keep-id 和宿主目录 owner 解决;:U 会递归改 owner,必须先在副本验证。
网络问题要从 backend 和用户态转发开始
Rootless 网络可能经过用户态组件,低端口、源地址、VPN、企业 DNS 和宿主回连的行为不能从 rootful bridge 推断。记录:
podman info --debug
podman network ls
podman network inspect podman项目验收至少覆盖容器出网、容器间 DNS、宿主访问发布端口、容器访问明确的宿主入口和端口冲突反例。低端口绑定失败时先改用高端口;确需改变系统非特权端口下限,应由主机 owner 评估整机影响,不能通过临时切到 rootful 掩盖设计问题。
Docker API Socket 是高权限兼容入口
依赖 Docker API 的 IDE 或测试库可以连接 Podman service。Linux rootless 常见入口是用户级 socket:
systemctl --user enable --now podman.socket
export DOCKER_HOST="unix://${XDG_RUNTIME_DIR}/podman/podman.sock"
curl --unix-socket "${XDG_RUNTIME_DIR}/podman/podman.sock" http://d/v1.40/_ping_ping 返回 OK 只证明连接可达。Build、network、volume、healthcheck、event、exec 和清理 API 仍要由真实项目夹具验证。把这个 socket 挂入容器,相当于把当前用户的容器控制权交给容器内进程;除非夹具明确需要且镜像可信,否则不挂载。网络暴露优先 SSH 转发或双向 TLS,不保留无认证 TCP 监听。
用户级 podman.socket 通过 systemd socket activation 按需启动服务,并没有把 Podman 变成一个拥有全部状态的中心 daemon。若登录会话结束后仍需服务,loginctl enable-linger 必须作为主机策略审查;不再需要兼容 API 时执行:
systemctl --user disable --now podman.socket
unset DOCKER_HOSTCompose 必须固定真正的 Provider
podman compose 调用外部 Compose Provider。两台机器即便都能执行该命令,也可能由不同程序解析同一 YAML:
podman compose version
podman compose --help
printf 'provider=%s\n' "${PODMAN_COMPOSE_PROVIDER:-auto-selection}"团队应在 containers.conf 或受控环境入口固定经过验证的 provider 路径和版本,然后保存 config 输出、健康状态、错误传播和 volume 保留结果。一个最小闭环是:
set -eu
provider="${PODMAN_COMPOSE_PROVIDER:?set an audited provider executable}"
test -x "$provider"
podman compose -f compose.yaml config
podman compose -f compose.yaml up -d
podman compose -f compose.yaml ps
podman compose -f compose.yaml logs --no-color > .te05-compose.log
podman compose -f compose.yaml down把宿主端口改为已占用端口后,up -d 应以非零退出,日志应指向 bind/listen 冲突。普通 down 后 named volume 应保留;只有数据 owner 同意重置时才执行 down --volumes。
Quadlet 负责长期服务,不负责掩盖容器参数
Linux 上需要由 systemd 管理开发辅助服务时,Quadlet 比手写一份调用 podman run 的 unit 更容易审查。Quadlet 基础用法把用户级定义放在 ~/.config/containers/systemd/,例如:
[Unit]
Description=Local preview service
[Container]
Image=registry.example.com/team/preview@sha256:<approved-digest>
ContainerName=te05-preview
PublishPort=127.0.0.1:18087:8080
ReadOnly=true
NoNewPrivileges=true
[Service]
Restart=on-failure
[Install]
WantedBy=default.target替换真实 Digest 后执行:
systemctl --user daemon-reload
systemctl --user start te05-preview.service
systemctl --user status te05-preview.service
journalctl --user -u te05-preview.service --no-pagerQuadlet 文件由 generator 转换为 systemd unit;不要编辑生成后的 unit。停止条件应包括进程退出、端口释放、日志可读和数据卷归属明确。用户服务是否随登录退出,同样受 linger 与会话策略影响。
代理、CA、Registry 与镜像身份
拉取链可能发生在本机 Podman、Podman Machine 内服务、Compose Provider 或容器内进程。宿主 curl 成功不能证明 VM 内的 Registry TLS 成功。取证至少包括:
podman info --debug
podman login --get-login registry.example.com
podman pull "registry.example.com/your-project/app@${IMAGE_DIGEST:?set approved digest}"
podman image inspect "registry.example.com/your-project/app@${IMAGE_DIGEST}"凭证进入系统批准的 auth store,不写入仓库、Containerfile、镜像层或构建日志。企业 CA 分别考虑宿主、Machine/运行时和容器镜像;关闭 TLS 校验只能作为隔离诊断,不能落成团队默认值。
故障先看边界,不先清空存储
| 现象 | 先取证 | 常见边界 | 修复后的反证 |
|---|---|---|---|
普通用户看不到 sudo podman 的镜像 | id、rootless、graphRoot | 两套用户存储 | 两边清单各自可解释,镜像用 Registry 迁移 |
| bind mount 只读或拒绝 | owner、UID map、SELinux AVC | 用户映射或标签 | 非 root 进程可写目标目录,其他目录仍拒绝 |
API _ping 成功但测试库失败 | 客户端日志、失败 API、service 日志 | 兼容字段不足 | 必需 API 夹具全部通过,或明确保留 Moby |
| Compose 在两台机器不同 | provider 路径、版本、config | 外部 provider 漂移 | 固定 provider 后归一化模型相同 |
| Machine 切 rootful 后对象消失 | connection、rootful、两套清单 | 默认连接切到另一存储 | 回切可见,重要制品已推 Registry |
全局 podman system reset 或无范围 prune 会摧毁证据,也可能影响同一用户的其他项目。先用 label、名称、project 和 Digest 缩小对象,再决定停止、删除容器、删除卷还是回收 cache。
升级、交接与退出
Podman 的团队合同至少包含支持平台、安装来源、版本窗口、rootless 策略、Machine 资源、网络 backend、Compose Provider、Socket 策略、Registry/CA、Quadlet owner 和项目清理入口。升级先在代表性 Linux、macOS、Windows、CPU 架构上运行同一正反夹具,再分批推广。
卸载前保存状态:
podman ps -a
podman images --digests
podman volume ls
podman network ls
podman system connection list
podman machine list业务卷先由 owner 导出并做恢复演练;短期凭证随后撤销;Socket 和用户服务停止;Machine 是否删除单独批准。清理只针对明确 project 与 te05-* 夹具,结束后重新运行清单,证明目标对象消失、其他项目仍存在。
如果团队是在 Docker、Podman 与 Rancher Desktop 之间做迁移,不应在这篇产品手册里继续堆另一套产品教程;用替代运行时选型与迁移对照项目事实,再进入对应产品主文。
