Podman 常用运维指令手册
适用版本:Podman 4.4+ / 5.x(稳定线)/ 6.x(当前)
宿主系统:RHEL 8/9/10、Fedora、Ubuntu 22.04+、Debian 12+、openEuler、macOS/Windows(Podman Machine)
约定:# root 或 sudo,$ 普通用户,<xxx> 为占位符。
差异标注:正文用 > **版本差异** 标注跨版本不一致之处,无标注表示 5.x/6.x 通用。
更新时间:2026-11(版本事实核对至 2026-11-05)
目录
- 定位与架构
- 版本与关键变更
- 安装部署
- 配置体系
- rootless 深入
- 镜像管理
- 容器运维
- Pod 与 kube play
- Quadlet(systemd 原生集成)
- systemd 集成与定时任务
- 卷与存储
- 网络
- Compose
- 自动更新与镜像签名
- Podman Machine
- 与 Docker 的差异与迁移
- 日志与监控
- 备份与迁移
- 卸载与清理
- 故障速查
- 附录
1. 定位与架构
1.1 无 daemon 意味着什么
Docker 是 C/S 架构:CLI → dockerd(root 常驻)→ containerd → runc。
Podman 是 fork-exec 架构:CLI 自己完成全部操作,通过 conmon 守护每个容器。
podman run
│
├─ 解析镜像(containers/image)
├─ 解包层(containers/storage)
├─ 建 netns(netavark)
├─ fork conmon ──┬─ fork runc → 业务进程
│ └─ 负责日志转发、退出状态回收
└─ CLI 退出(容器继续跑)
| 维度 |
影响 |
| 没有常驻 daemon |
无单点故障;systemctl restart 不存在;不能"重启 podman 服务" |
| 每个容器一个 conmon |
容器数量多时进程数多(ps 会看到大量 conmon) |
| 无 API 默认监听 |
远程操作需 podman system service 显式开 socket |
| 权限模型跟随调用者 |
普通用户跑就是 rootless,root 跑就是 rootful |
1.2 与 Docker 的架构对照
| 能力 |
Docker |
Podman |
| 运行时守护 |
dockerd 常驻 |
无(conmon 随容器) |
| 容器父进程 |
containerd-shim |
conmon |
| 镜像构建 |
BuildKit(内置) |
Buildah(库形式调用) |
| 镜像搬运 |
内置 |
Skopeo(库形式调用) |
| 网络 |
libnetwork + docker-proxy |
Netavark + aardvark-dns + pasta |
| rootless |
需额外配置,功能受限 |
原生支持 |
| k8s 集成 |
需 cri-dockerd |
原生 kube play / Quadlet .kube |
| API |
REST 常驻 |
REST 按需(system service) |
1.3 什么时候选 Podman
适合:单机多租户、需要普通用户跑容器、RHEL 系(系统自带)、边缘/嵌入式、想用 systemd 原生管理容器、k8s YAML 直接落地到单机。
不适合:需要 Docker Swarm、依赖 Docker 专有 API 的工具链、需要 docker build 之外的特殊 BuildKit 特性(如 --mount=type=secret 需 buildah 版本配合)、团队工具链强绑 docker.sock。
2. 版本与关键变更
2.1 版本事实(2026-11)
| 项目 |
状态 |
| 稳定线 |
Podman 5.8.x(5.8.9 / 2026-10-12) |
| 新版 |
Podman 6.0 已发布 |
| 底层配套 |
Buildah 1.43、containers/storage 1.62、containers/image 5.39、Netavark、Pasta |
| 网络后端 |
5.x 默认 netavark;4.0 以前是 CNI |
| 数据库 |
5.8 起自动把 BoltDB 迁移到 SQLite;6.0 移除 BoltDB |
2.2 各版本关键变化
| 版本 |
变化 |
运维影响 |
| 4.4 |
podman generate systemd 标记弃用 |
新部署改用 Quadlet |
| 4.9 |
podman farm 多机构建 |
跨架构构建更简单 |
| 5.0 |
全新 podman machine;Netavark 默认;pasta 默认 |
无 daemon 的 Mac/Win 体验改善 |
| 5.2 |
--device 与 --privileged 同时生效(不再忽略) |
特权容器行为变化 |
| 5.4 |
podman update 支持健康检查;--hosts-file |
可不重建容器改配置 |
| 5.8 |
自动迁移 SQLite;podman update --ulimit;exec --no-session |
升级后首次启动会迁移数据库 |
| 6.0 |
移除 BoltDB;网络彻底转 Netavark/Pasta/nftables;Quadlet REST API |
升级前必须确认 nftables 与内核版本 |
版本差异:Podman 6.0 的迁移是不可逆的(BoltDB → SQLite)。升级前必须备份 ~/.local/share/containers/storage 与 /var/lib/containers/storage。
2.3 版本识别
podman version
# Client: Podman Engine
# Version: 5.8.9
# API Version: 5.8.9
# Go Version: go1.24.x
# Built: Thu Oct 12 ...
# OS/Arch: linux/amd64
podman info
podman info --format '{{.Version.Version}} {{.Host.CgroupVersion}} {{.Host.NetworkBackend}} {{.Host.Security.Rootless}}'
podman info --format '{{json .Host}}' | jq
# 配套工具版本
buildah --version
skopeo --version
runc --version
crun --version
pasta --version
netavark --version
3. 安装部署
3.1 RHEL / CentOS Stream / Rocky / Alma / openEuler
# 系统自带,直接装
dnf install -y podman
# 按需补充
dnf install -y podman-docker # 提供 docker 命令兼容包装
dnf install -y podman-compose # 或用 docker-compose(见 13 章)
dnf install -y buildah skopeo # 构建与镜像搬运
dnf install -y netavark aardvark-dns pasta # 网络三件套(9+ 默认已装)
dnf install -y podman-tui # 文本界面(可选)
dnf install -y udica # SELinux 策略生成
podman version
3.2 Ubuntu / Debian
# 官方源(版本较旧,但稳定)
apt update && apt install -y podman
# 需要新版本(5.x/6.x):openSUSE Kubic 源
. /etc/os-release
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" \
> /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
curl -fsSL "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key" \
| gpg --dearmor -o /etc/apt/trusted.gpg.d/kubic.gpg
apt update && apt install -y podman
# rootless 依赖(Ubuntu 需手动装)
apt install -y uidmap slirp4netns fuse-overlayfs passt
坑点:Ubuntu 上 rootless Podman 需要 uidmap(提供 newuidmap/newgidmap)。缺了会报 cannot clone: Operation not permitted 或 newuidmap: not found。
3.3 Fedora(版本最新)
dnf install -y podman podman-docker buildah skopeo
# Fedora 通常最快拿到最新 Podman
3.4 macOS / Windows
# 方式一:Podman Desktop(图形化,推荐新手)
# https://podman-desktop.io
# 方式二:命令行
brew install podman # macOS
winget install RedHat.Podman # Windows
# 初始化虚拟机(首次必须)
podman machine init
podman machine start
podman machine list
podman machine ssh # 进 VM
podman run --rm hello-world
3.5 安装后验证清单
podman info # 无报错即基本可用
podman run --rm hello-world
podman run --rm alpine id # rootless 下 uid=0 但映射到宿主普通 UID
# rootless 关键项
podman unshare cat /proc/self/uid_map # 应有 0 1000 1 / 1 100000 65536
loginctl show-user $USER | grep Linger # 需 Linger=yes
sysctl net.ipv4.ip_unprivileged_port_start # 需 ≤ 80 才能绑 80 端口
# 网络
podman network ls
podman network inspect podman
# 存储
podman system df
podman info --format '{{.Store.GraphRoot}}'
# 让 docker 命令走 podman(podman-docker 提供)
dnf install -y podman-docker
docker version # 会提示 "Emulate Docker CLI using podman"
systemctl enable --now podman.socket # 让 docker API 客户端可用
curl --unix-socket /run/podman/podman.sock http://d/v1.40/libpod/version
4. 配置体系
4.1 四类配置文件
| 文件 |
作用 |
系统级 |
用户级 |
containers.conf |
引擎行为(运行时、cgroup、日志、网络) |
/etc/containers/containers.conf |
~/.config/containers/containers.conf |
registries.conf |
镜像仓库、短名解析、镜像加速 |
/etc/containers/registries.conf |
~/.config/containers/registries.conf |
storage.conf |
存储驱动、graphroot、额外镜像存储 |
/etc/containers/storage.conf |
~/.config/containers/storage.conf |
policy.json |
镜像签名验证策略 |
/etc/containers/policy.json |
~/.config/containers/policy.json |
优先级:命令行参数 > 用户级 > 系统级 > 内置默认。配置文件本身不合并,用户级只要存在就整体覆盖系统级(同类配置项)。
# 查看最终生效配置(重要,排查配置不生效必用)
podman info
podman info --format '{{json .Host}}' | jq
podman system check
4.2 containers.conf 常用段
# /etc/containers/containers.conf
[containers]
# 默认环境变量
env = ["TZ=Asia/Shanghai", "LANG=zh_CN.UTF-8"]
# 默认 ulimit
default_ulimits = ["nofile=65536:65536"]
# 默认日志驱动
log_driver = "journald"
# 默认 DNS
dns_servers = ["10.0.0.2"]
# 默认停止超时
stop_timeout = 30
# 禁止在容器内看到宿主 /dev
devices = []
# 默认 seccomp
seccomp_profile = "/usr/share/containers/seccomp.json"
# 默认 capabilities
default_capabilities = [
"CHOWN","DAC_OVERRIDE","FOWNER","FSETID","KILL","NET_BIND_SERVICE",
"SETFCAP","SETGID","SETPCAP","SETUID","SYS_CHROOT"
]
[engine]
# 运行时(crun 更快、内存占用低;runc 兼容性最好)
runtime = "crun"
# 镜像并行拉取
image_parallel_copies = 0
# 退出时清理
# 事件日志后端
events_logger = "journald"
# 临时目录
tmp_dir = "/var/tmp"
[network]
# 网络后端:netavark(默认)/ cni
network_backend = "netavark"
# 默认网络配置
default_network = "podman"
# 默认子网(避免与内网冲突)
default_subnet = "10.88.0.0/16"
# 查看默认配置(带注释的完整版)
cat /usr/share/containers/containers.conf
podman --help | head -40
4.3 registries.conf(镜像加速与短名解析)
# /etc/containers/registries.conf
unqualified-search-registries = ["docker.io", "harbor.internal"]
# 镜像加速
[[registry]]
prefix = "docker.io"
location = "docker.m.daocloud.io"
insecure = false
# 私有仓库(HTTP)
[[registry]]
prefix = "harbor.internal"
location = "harbor.internal:80"
insecure = true
# 镜像别名(把公网镜像指向内部镜像)
[[registry]]
prefix = "docker.io/library/nginx"
location = "harbor.internal/mirror/nginx"
# 也可以用分片目录(更干净)
mkdir -p /etc/containers/registries.conf.d
cat >/etc/containers/registries.conf.d/00-mirror.conf <<'EOF'
[[registry]]
prefix = "docker.io"
location = "docker.m.daocloud.io"
EOF
# 验证短名解析
podman search nginx
podman pull nginx # 会按 unqualified-search-registries 顺序找
podman pull docker.io/library/nginx:1.25 # 显式全名,最稳
坑点:短名(nginx)需要交互式选择仓库,脚本里会失败。生产脚本一律写全名 docker.io/library/nginx:1.25。
4.4 storage.conf(多存储与驱动)
# /etc/containers/storage.conf
[storage]
driver = "overlay"
# rootless 在内核不支持 overlay 时用 fuse-overlayfs
# driver = "fuse-overlayfs"
graphroot = "/var/lib/containers/storage"
runroot = "/run/containers/storage"
[storage.options.overlay]
# 单容器可写层上限(需 xfs pquota)
# size = "20G"
mount_program = "/usr/bin/fuse-overlayfs" # 仅 fuse-overlayfs 模式
mountopt = "nodev,metacopy=on"
podman info --format '{{.Store.GraphDriverName}} {{.Store.GraphRoot}}'
podman system df
4.5 配置生效
# 大多数配置改完直接生效(无 daemon 可重启)
podman info | grep -A3 network
# 个别需要重载:storage.conf 改了 driver 需迁移
podman system migrate
podman system reset # ★ 清空所有镜像容器(危险)
5. rootless 深入
5.1 原理
rootless Podman 用 user namespace 把容器内的 root 映射到宿主的一个非特权 UID 区间:
容器内 uid 0 → 宿主 uid 1000(你的账号)
容器内 uid 1..65536 → 宿主 uid 100000..165535(subuid 区间)
所以容器内的 root 在宿主上只是普通用户 —— 即使逃逸也拿不到真正的 root。
# 查看映射
podman unshare cat /proc/self/uid_map
# 0 1000 1
# 1 100000 65535
podman unshare cat /proc/self/gid_map
5.2 前置条件逐项检查
# 1) subuid/subgid
grep "^$USER" /etc/subuid /etc/subgid
# 无输出则添加:
usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
# 或直接编辑 /etc/subuid、/etc/subgid 加一行:user:100000:65536
# 2) newuidmap/newgidmap(uidmap 包)
which newuidmap newgidmap
# 必须带 suid 位
ls -l /usr/bin/newuidmap
# 3) 用户命名空间上限
sysctl user.max_user_namespaces
# Ubuntu 还需
cat /proc/sys/kernel/unprivileged_userns_clone # 需 1
# 4) 低端口绑定
sysctl net.ipv4.ip_unprivileged_port_start
# 需要绑 80/443 时设 80
echo 'net.ipv4.ip_unprivileged_port_start=80' >/etc/sysctl.d/99-podman.conf
sysctl --system
# 5) linger(退出登录后容器继续跑)
loginctl enable-linger $USER
loginctl show-user $USER | grep Linger
# 6) cgroup v2 委派(systemd 用户会话要能管 cgroup)
stat -fc %T /sys/fs/cgroup/ # 需 cgroup2fs
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.controllers
# 应有 cpu memory pids 等
5.3 cgroup v2 委派问题(rootless 最常见的坑)
现象:rootless 容器里 --memory 不生效、podman stats 无数据、systemd 报 Failed to create cgroup。
# 检查当前会话的委派
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.subtree_control
# 空或只有 pids → 委派不全
# 解决:让 systemd 用户会话委派 cgroup
# Debian/Ubuntu 需在 /etc/systemd/system/user@.service.d/delegate.conf
mkdir -p /etc/systemd/system/user@.service.d
cat >/etc/systemd/system/user@.service.d/delegate.conf <<'EOF'
[Service]
Delegate=yes
EOF
systemctl daemon-reload
# 需要重新登录(或重启 user@<uid>.service)才生效
# 验证
systemctl restart user@$(id -u).service
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.controllers
5.4 rootless 的存储限制
| 限制 |
说明 |
绕过 |
只能用 overlay(内核 ≥ 5.11)或 fuse-overlayfs |
老内核不支持非特权 overlay |
装 fuse-overlayfs |
| 默认不能读写宿主 root 的文件 |
uid 映射决定 |
podman unshare 下操作 |
--privileged 能力受限 |
仍是用户态 |
需要真特权就用 rootful |
| 不能绑 < 1024 端口(默认) |
内核限制 |
ip_unprivileged_port_start |
| 网络不能用某些高级特性 |
pasta/netavark 用户态 |
用 rootful 或 --network host |
# 在映射后的 namespace 里改文件属主(rootless 操作卷数据的正确姿势)
podman unshare chown -R 1000:1000 /data/app
podman unshare ls -ln /data/app
podman unshare rm -rf /data/app/cache
5.5 rootless vs rootful 的选择
# 同一台机器可以共存,存储完全隔离
podman info --format '{{.Store.GraphRoot}}'
# rootful: /var/lib/containers/storage
# rootless: ~/.local/share/containers/storage
| 场景 |
选择 |
| 开发、CI、多租户 |
rootless |
| 需要绑 80/443 且不想调内核参数 |
rootful 或 rootless + sysctl |
需要 --privileged、真实设备、NFS 挂载 |
rootful |
| 系统服务(开机自启、与系统生命周期一致) |
rootful + Quadlet |
| 个人服务(用户级自启) |
rootless + --user + linger |
注意:rootless 与 rootful 的镜像是两套,podman images 结果不同。切换身份排查时先确认 podman info --format '{{.Store.GraphRoot}}'。
6. 镜像管理
6.1 基础命令
podman images
podman images -a
podman images --filter dangling=true
podman images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.Created}}'
podman pull docker.io/library/nginx:1.25
podman pull --arch arm64 docker.io/library/nginx:1.25
podman pull --os linux --arch amd64 --variant v8 nginx
podman pull --tls-verify=false harbor.internal:5000/app:1.0
podman pull --authfile ~/.config/containers/auth.json harbor.internal/app:1.0
podman search nginx
podman search --limit 10 --filter is-official=true nginx
podman inspect nginx:1.25
podman inspect --format '{{.Config.Env}}' nginx:1.25
podman history nginx:1.25
podman tag nginx:1.25 harbor.internal/web/nginx:1.25.3
podman push harbor.internal/web/nginx:1.25.3
podman push --all-tags harbor.internal/web/nginx
podman rmi nginx:1.25
podman image prune -a
6.2 登录与凭据
podman login harbor.internal -u admin --password-stdin < ~/.pass
podman login --tls-verify=false 10.0.0.50:5000
podman logout harbor.internal
# 凭据文件(与 Docker 格式兼容,可复用)
cat ~/.config/containers/auth.json
cat ${XDG_RUNTIME_DIR}/containers/auth.json
# 多用户共享凭据(rootful 全局)
cat /run/containers/0/auth.json
6.3 导入导出
# OCI 归档(推荐,保留元数据)
podman save --format oci-archive -o nginx-oci.tar nginx:1.25
podman load -i nginx-oci.tar
# docker-archive(与 docker load 互通)
podman save --format docker-archive -o nginx-docker.tar nginx:1.25
docker load -i nginx-docker.tar
# 目录形式(可查看、增量)
podman save --format oci-dir -o ./nginx-oci nginx:1.25
# 容器文件系统快照
podman export -o rootfs.tar <container>
podman import rootfs.tar myapp:snapshot
# 压缩
podman save nginx:1.25 | gzip > nginx.tar.gz
6.4 skopeo(不落地搬运镜像)
# 镜像复制(跨仓库/跨机器,无需本地存储)
skopeo copy docker://docker.io/library/nginx:1.25 \
docker://harbor.internal/mirror/nginx:1.25
# 检查远端镜像(不拉取)
skopeo inspect docker://docker.io/library/nginx:1.25
skopeo inspect --raw docker://docker.io/library/nginx:1.25 | jq
# 删除远端镜像
skopeo delete docker://harbor.internal/mirror/nginx:1.25
# 同步整个仓库(备份/迁移神器)
skopeo sync --src docker --dest dir docker.io/library/nginx /backup/images
skopeo sync --src dir --dest docker /backup/images harbor.internal/mirror
skopeo sync --src yaml --dest docker sync.yaml harbor.internal/
# 批量复制所有 tag
skopeo list-tags docker://docker.io/library/nginx | jq -r '.Tags[]' | \
while read t; do skopeo copy docker://nginx:$t docker://harbor.internal/mirror/nginx:$t; done
6.5 buildah 构建
# Dockerfile 构建(podman build 内部就是 buildah)
podman build -t myapp:1.0.0 .
podman build -f docker/Dockerfile.prod -t myapp:1.0.0 .
podman build --build-arg VERSION=1.0.0 .
podman build --layers -t myapp:1.0.0 . # 启用层缓存
podman build --no-cache -t myapp:1.0.0 .
podman build --platform linux/amd64,linux/arm64 -t myapp:1.0.0 --manifest multi .
# buildah 脚本式构建(无需 Dockerfile)
buildah from scratch
buildah add <ctr> ./app /app
buildah config --entrypoint '["/app"]' <ctr>
buildah commit <ctr> myapp:1.0.0
# 多架构 manifest
podman manifest create myapp:multi
podman build --platform linux/amd64 -t myapp:amd64 .
podman manifest add myapp:multi containers-storage:localhost/myapp:amd64
podman manifest push --all myapp:multi docker://harbor.internal/web/myapp:1.0.0
# 检查
podman manifest inspect myapp:multi
版本差异:Podman 5.x 的 podman build 需要 buildah 后端(多数发行版已内置);--layers 在 rootless + fuse-overlayfs 下缓存效果较差,建议用 overlay。
6.6 镜像清理
podman system df
podman image prune # 悬空
podman image prune -a # 无引用
podman image prune -a --filter until=720h
podman system prune # 容器+镜像+网络+构建缓存
podman system prune -a --volumes # ★ 含卷,危险
7. 容器运维
7.1 run 参数(与 docker 的差异点标注)
podman run -d \
--name web \
--restart unless-stopped \
-p 8080:80 \
-e TZ=Asia/Shanghai \
-v /data/web:/usr/share/nginx/html:ro \
--network appnet \
--cpus 2 -m 2g \
--health-cmd "curl -fs http://127.0.0.1/healthz || exit 1" \
--health-interval 30s --health-retries 3 \
--label env=prod \
docker.io/library/nginx:1.25
Podman 特有/行为不同的参数
| 参数 |
差异 |
--userns=keep-id |
把当前用户 UID 映射进容器同 UID(rootless 读写宿主文件必备) |
--userns=host |
不做用户命名空间隔离(接近 rootful 行为) |
--userns=auto |
自动分配 UID 区间,支持 size= |
--group-add keep-groups |
保留宿主附加组(访问 NFS/共享目录必需) |
--security-opt label=disable |
关闭 SELinux 标签(临时排障) |
--security-opt label=type:container_t |
指定 SELinux 类型 |
--sdnotify=conmon |
让 systemd 感知容器就绪 |
--restart |
支持 no/on-failure/always/unless-stopped,但底层是 podman-restart.service |
--http-proxy |
默认把宿主代理环境变量注入容器(Docker 不注入) |
--passwd |
自动生成 /etc/passwd(解决 uid 无用户名问题) |
--replace |
同名容器存在时直接替换(比 rm && run 方便) |
# rootless 读写宿主目录的三种正确姿势
podman run --userns=keep-id -v /home/user/data:/data alpine touch /data/x
podman run -v /home/user/data:/data:Z alpine touch /data/x # SELinux 场景
podman unshare chown -R 1000:1000 /home/user/data && podman run -v /home/user/data:/data:Z alpine touch /data/x
# 访问宿主附加组才能读的目录(如 NFS)
podman run --group-add keep-groups -v /mnt/nfs:/data alpine ls /data
7.2 生命周期
podman ps
podman ps -a
podman ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}\t{{.Image}}'
podman ps --filter status=exited
podman ps --filter label=env=prod
podman ps -s # 含可写层大小
podman start web
podman stop web
podman stop -t 30 web
podman restart web
podman kill -s SIGHUP web
podman pause web && podman unpause web
podman rm web
podman rm -f web
podman rm -v web # 连匿名卷
podman rename web web-old
# 热更新(5.4+)
podman update --memory 4g --cpus 4 web
podman update --env TZ=UTC --env APP_ENV=prod web
podman update --unsetenv DEBUG web
podman update --ulimit nofile=4096:8192 web # 5.8+
podman update --restart=no web
podman update --health-cmd "..." web
7.3 执行与调试
podman exec -it web sh
podman exec -u root web id
podman exec -w /app web ls
podman exec -e LANG=C web locale
podman exec --no-session -it web sh # 5.8+,短命令更快
podman attach web
podman top web
podman diff web
podman stats
podman stats --no-stream
podman healthcheck run web
podman inspect web --format '{{json .State}}' | jq
# 生成 k8s 风格 yaml(迁移/审计)
podman generate kube web > web.yaml # 旧写法(已弃用)
podman kube generate web > web.yaml # 6.0 推荐
7.4 退出码与 OOM
podman inspect web --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
journalctl --user -u container-web.service -n 50 # Quadlet 起的容器
dmesg -T | grep -i 'killed process'
| 码 |
含义 |
| 0 |
正常 |
| 1 |
应用错误 |
| 125 |
podman 自身错误 |
| 126/127 |
命令不可执行/不存在 |
| 137 |
SIGKILL / OOM |
| 143 |
SIGTERM |
7.5 提交与快照
podman commit -m "hotfix" -a "ops" web myapp:hotfix
podman export web | gzip > web-$(date +%F).tar.gz
8. Pod 与 kube play
8.1 Pod 概念
Podman 的 pod 与 k8s 的 pod 语义一致:共享网络命名空间(同一 IP、同一端口空间)、可共享 IPC/UTS、可共享存储卷。实现上用一个 infra 容器(pause)持有 namespace。
版本差异:Podman 5.x 起 infra 容器使用最小根文件系统里内置的 catatonit,不再依赖 pause 镜像(老版本会看到 k8s.gcr.io/pause 镜像)。
# 创建 pod(发布端口在 pod 级别)
podman pod create --name webpod -p 8080:80 -p 9090:9090
podman pod ls
podman pod inspect webpod
podman pod ps --format 'table {{.Name}}\t{{.Status}}\t{{.NumContainers}}\t{{.InfraID}}'
# 往 pod 里加容器(不要写 -p,端口在 pod 上)
podman run -d --pod webpod --name web docker.io/library/nginx:1.25
podman run -d --pod webpod --name sidecar docker.io/library/busybox:1.36 sleep 3600
# 生命周期
podman pod stop webpod
podman pod start webpod
podman pod restart webpod
podman pod pause webpod
podman pod rm -f webpod
podman pod prune
# 资源与统计
podman pod stats
podman pod top webpod
8.2 Pod 内共享卷
podman volume create shared
podman pod create --name apppod -p 8080:80 -v shared:/shared
podman run -d --pod apppod --name writer -v shared:/data alpine sh -c 'while true; do date >> /data/t.log; sleep 5; done'
podman run -d --pod apppod --name reader -v shared:/data:ro alpine tail -f /data/t.log
8.3 kube play(单机跑 k8s YAML)
# web.yaml
apiVersion: v1
kind: Pod
metadata:
name: web
labels: { app: web }
spec:
containers:
- name: nginx
image: docker.io/library/nginx:1.25
ports:
- containerPort: 80
hostPort: 8080
env:
- name: TZ
value: Asia/Shanghai
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
hostPath: { path: /data/web, type: Directory }
podman kube play web.yaml
podman kube play --down web.yaml
podman kube play --replace web.yaml
podman kube play --network appnet web.yaml
podman kube play --publish 8080:80 web.yaml
# 支持的资源:Pod、Deployment、DaemonSet、Job、CronJob、ConfigMap、Secret、PersistentVolumeClaim
# 不支持的:Service(部分)、Ingress、CRD、RBAC
# 从容器反向生成(迁移到 k8s 用这个)
podman kube generate web > web.yaml
podman kube generate --type deployment web > web-deploy.yaml
实践建议:kube play 适合开发/边缘/单机交付。它的 Service 支持有限,网络语义与 k8s 不完全一致,不要用它模拟生产 k8s。
9. Quadlet(systemd 原生集成)
9.1 为什么用 Quadlet
| 方式 |
状态 |
问题 |
podman run 手工 |
— |
重启丢失,无自启 |
--restart |
可用 |
依赖 podman-restart.service,顺序不可控 |
podman generate systemd |
弃用(4.4+) |
生成静态 unit,改配置要重新生成 |
| Quadlet |
官方推荐(5.x+) |
声明式、由 systemd 生成器解析、与 systemd 深度集成 |
9.2 文件位置与命名
| 类型 |
后缀 |
系统级 |
用户级 |
| 容器 |
.container |
/etc/containers/systemd/ |
~/.config/containers/systemd/ |
| 卷 |
.volume |
同上 |
同上 |
| 网络 |
.network |
同上 |
同上 |
| Pod |
.pod |
同上 |
同上 |
| Kube |
.kube |
同上 |
同上 |
| 镜像构建 |
.build |
同上 |
同上 |
文件名即服务名的前缀:web.container → web.service。
9.3 .container 完整语法
# /etc/containers/systemd/web.container
[Unit]
Description=Web frontend
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target
# 依赖其他 Quadlet 单元
Requires=db.service
After=db.service
[Container]
# 镜像(支持变量)
Image=docker.io/library/nginx:1.25
ContainerName=web
# 端口(写法同 -p)
PublishPort=8080:80
PublishPort=127.0.0.1:9090:9090
# 卷(支持具名卷 xxx.volume 与宿主路径)
Volume=web-html.volume:/usr/share/nginx/html:ro,Z
Volume=/data/logs:/var/log/nginx:Z
# 环境变量
Environment=TZ=Asia/Shanghai
Environment=APP_ENV=prod
EnvironmentFile=/etc/web.env
# 网络
Network=appnet.network
NetworkAlias=frontend
# 资源限制
PodmanArgs=--cpus=2 --memory=2g
# 健康检查
HealthCmd=curl -fs http://127.0.0.1/healthz || exit 1
HealthInterval=30s
HealthTimeout=3s
HealthRetries=3
HealthStartPeriod=20s
HealthOnFailure=restart
# 自动更新
AutoUpdate=registry
# 启动顺序依赖挂载
RequiresMountsFor=/data/logs
# SELinux
SecurityLabelType=container_t
# 只读根 + 临时目录
ReadOnly=true
Tmpfs=/tmp:size=64m
# 用户
User=1000:1000
# 日志
LogDriver=journald
# 拉取策略:always / missing / never / newer
Pull=newer
[Service]
Restart=always
RestartSec=5
TimeoutStartSec=300
TimeoutStopSec=60
# 优雅停止
KillSignal=SIGTERM
[Install]
WantedBy=multi-user.target
常用键对照表
| Quadlet 键 |
等价 podman 参数 |
Image |
镜像位置参数 |
ContainerName |
--name |
PublishPort |
-p |
Volume |
-v |
Environment |
-e |
EnvironmentFile |
--env-file |
Network |
--network |
Pod |
--pod |
User / Group |
--user |
ReadOnly |
--read-only |
Tmpfs |
--tmpfs |
PodmanArgs |
任意其他参数(逃生口) |
AutoUpdate |
--label io.containers.autoupdate=registry |
HealthCmd 等 |
--health-* |
Pull |
--pull |
9.4 .volume / .network / .pod
# /etc/containers/systemd/web-html.volume
[Volume]
VolumeName=web-html
Driver=local
# 绑定到宿主目录
# Driver=image 不需要
Options=type=none,device=/data/html,o=bind
# /etc/containers/systemd/appnet.network
[Network]
NetworkName=appnet
Driver=bridge
Subnet=10.89.0.0/24
Gateway=10.89.0.1
# 内部网络(不出网)
Internal=true
# 端口转发
PublishPort=8080:80
# /etc/containers/systemd/webpod.pod
[Pod]
PodName=webpod
PublishPort=8080:80
Network=appnet.network
# 容器加入 pod
[Container]
Image=docker.io/library/nginx:1.25
Pod=webpod.pod
9.5 .kube(直接跑 k8s YAML)
# /etc/containers/systemd/app.kube
[Unit]
Description=App from k8s yaml
[Kube]
Yaml=/etc/containers/kubernetes/app.yaml
# 覆盖 YAML 中的镜像
# Set WorkingDir=/opt
# PublishPort=8080:80
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
9.6 部署与验证流程
# 1) 放文件
cp web.container /etc/containers/systemd/
# 2) 重载生成器(关键:不 reload 就没有 service)
systemctl daemon-reload
# 3) 查看生成的单元
systemctl cat web.service
# 4) 启动
systemctl start web.service
systemctl status web.service
# 5) 日志
journalctl -u web.service -f
# 6) 语法检查(不实际启动)
/usr/libexec/podman/quadlet -dryrun -f /etc/containers/systemd/web.container
# 或
/usr/libexec/podman/quadlet -dryrun -v
# 7) 调试:看生成器输出
/usr/libexec/podman/quadlet -version
journalctl -u web.service --no-pager | head -30
# 8) 变更后
systemctl daemon-reload && systemctl restart web.service
9.7 用户级 Quadlet(rootless 自启)
mkdir -p ~/.config/containers/systemd
cp web.container ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start web.service
systemctl --user status web.service
journalctl --user -u web.service -f
# 必须开 linger,否则退出登录容器就停
loginctl enable-linger $USER
loginctl show-user $USER | grep Linger
# 开机自启(user 级默认跟随 linger)
systemctl --user is-enabled web.service
9.8 Quadlet 常见错误
| 报错 |
原因 |
处理 |
Failed to start web.service: Unit web.service not found |
没 daemon-reload 或后缀/路径错 |
确认在正确目录,daemon-reload |
no such image |
Pull=never 且本地无镜像 |
先 podman pull 或改 Pull=missing |
Error: creating container storage: ... exists |
同名容器已存在(非 Quadlet 创建) |
podman rm -f <name> |
| 启动后立刻退出 |
容器内进程非前台/秒退 |
journalctl 看日志 |
| 权限拒绝(SELinux) |
卷缺 :Z |
Volume=/data:/data:Z |
Unit is masked |
被 mask |
systemctl unmask web.service |
| 改了 .container 不生效 |
未 reload |
systemctl daemon-reload && restart |
10. systemd 集成与定时任务
10.1 generate systemd(仅维护旧部署时使用)
# 已弃用,但仍可用
podman generate systemd --new --name web --files --restart-policy=always
# 输出 container-web.service 到当前目录
cp container-web.service /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now container-web
# --new:每次启动重建容器(配置跟随 systemd,推荐)
# 不加 --new:复用已有容器(容器被删就起不来)
新部署请直接用 Quadlet。generate systemd 在 Podman 6.0 中仍在,但已无新特性支持。
10.2 podman-restart.service
# 让 --restart=always 的容器在开机后自动起来
systemctl enable --now podman-restart.service
systemctl status podman-restart
# 用户级
systemctl --user enable --now podman-restart.service
# 注意:它只拉起标记了 restart policy 的容器,且不保证顺序
10.3 定时任务
# 方式 A:systemd timer(推荐)
cat >/etc/systemd/system/backup-container.service <<'EOF'
[Unit]
Description=Backup container data
[Service]
Type=oneshot
ExecStart=/usr/bin/podman run --rm -v app-data:/data:ro -v /backup:/backup:Z \
docker.io/library/alpine tar czf /backup/app-$(date +%%F).tar.gz -C /data .
EOF
cat >/etc/systemd/system/backup-container.timer <<'EOF'
[Unit]
Description=Daily container backup
[Timer]
Persistent=true
RandomizedDelaySec=600
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now backup-container.timer
systemctl list-timers --all
# 方式 B:cron(rootless 需注意 PATH 与 XDG_RUNTIME_DIR)
crontab -e
# 0 2 * * * XDG_RUNTIME_DIR=/run/user/$(id -u) /usr/bin/podman run --rm alpine echo hi
坑点:cron 里跑 rootless podman 必须显式设 XDG_RUNTIME_DIR=/run/user/<uid>,否则报 cannot access storage。systemd timer 无此问题。
11. 卷与存储
11.1 卷操作
podman volume create app-data
podman volume create --opt type=none --opt device=/data/app --opt o=bind app-bind
podman volume create --opt type=nfs --opt o=addr=10.0.0.40,rw,nfsvers=4.1 \
--opt device=:/export/data nfs-data
podman volume ls
podman volume ls --filter dangling=true
podman volume inspect app-data
podman volume rm app-data
podman volume prune
podman volume export app-data -o app-data.tar # 直接导出
podman volume import app-data app-data.tar
# 物理路径
podman volume inspect app-data -f '{{.Mountpoint}}'
11.2 存储驱动
podman info --format '{{.Store.GraphDriverName}}'
# overlay(rootful 默认,rootless 内核 ≥5.11 也可)
# fuse-overlayfs(rootless 老内核回退)
# vfs(慢,兼容性最好)
# btrfs / zfs
# rootless 强制用 fuse-overlayfs
# ~/.config/containers/storage.conf
# [storage]
# driver = "fuse-overlayfs"
# [storage.options]
# mount_program = "/usr/bin/fuse-overlayfs"
11.3 空间治理
podman system df
podman system df -v
du -sh $(podman info --format '{{.Store.GraphRoot}}')
du -sh $(podman info --format '{{.Store.GraphRoot}}')/overlay
# 容器可写层
podman ps -s --format 'table {{.Names}}\t{{.Size}}'
# 日志位置(journald 走 journal;k8s-file 走文件)
podman inspect web -f '{{.HostConfig.LogConfig.Type}}'
ls -l $(podman info --format '{{.Store.RunRoot}}')/containers/
11.4 权限与 SELinux
# rootless 下容器写宿主目录
podman unshare chown -R 1000:1000 /data/app
podman run --userns=keep-id -v /data/app:/app:Z ...
# 检查标签
ls -Z /data/app
# 正确:system_u:object_r:container_file_t:s0
# 永久策略
semanage fcontext -a -t container_file_t "/data/app(/.*)?"
restorecon -Rv /data/app
# 自动生成策略(推荐,比手工精确)
dnf install -y udica
podman inspect web > web.json
udica -j web.json web_container
semodule -i web_container.pp
12. 网络
12.1 网络后端演进
| 版本 |
默认后端 |
组件 |
| ≤ 4.0 |
CNI |
cni-plugins + dnsname |
| 4.1 ~ 5.x |
netavark |
netavark + aardvark-dns |
| 6.0 |
netavark + pasta |
弱化 slirp4netns 与 iptables,走 nftables |
podman info --format '{{.Host.NetworkBackend}}'
# netavark
rpm -q netavark aardvark-dns
12.2 网络模式
podman run --network podman ... # 默认 bridge(名为 podman)
podman run --network host ... # 共享宿主网络(rootless 也可,见下)
podman run --network none ... # 无网络
podman run --network container:web ... # 共享另一容器
podman run --network appnet ... # 自定义网络
podman run --network pasta ... # 显式用 pasta(5.x+)
podman run --network slirp4netns:port_handler=slirp4netns ... # 老方案
podman run --network private ... # 仅 lo + 端口转发
rootless 的 host 网络:Podman 支持 rootless --network host(共享宿主 netns,无需额外权限)。这是与 Docker rootless 的重要差异——Docker rootless 的 host 模式受限。
12.3 自定义网络
podman network create appnet
podman network create --subnet 10.89.5.0/24 --gateway 10.89.5.1 appnet
podman network create --internal backend # 不出网
podman network create --driver macvlan \
--opt parent=eth0 --subnet 192.168.1.0/24 --gateway 192.168.1.1 mvneta
podman network ls
podman network inspect appnet
podman network connect appnet web
podman network disconnect appnet web
podman network rm appnet
podman network prune
# 容器内 DNS(aardvark-dns 提供,支持容器名解析)
podman run -d --name db --network appnet docker.io/library/mysql:8.4
podman run -d --name app --network appnet docker.io/library/alpine sleep 3600
podman exec app ping -c1 db
podman exec app cat /etc/resolv.conf
12.4 端口发布与 rootless
podman run -d -p 8080:80 nginx
podman run -d -p 127.0.0.1:8080:80 nginx
podman port web
podman inspect web -f '{{json .NetworkSettings.Ports}}'
# rootless 绑 80 端口
sysctl net.ipv4.ip_unprivileged_port_start=80
# 或 rootless 用 pasta 的 --map-guest-addr 等高级特性
12.5 网络排障
podman network inspect appnet | jq '.[0].subnets'
podman exec web ip addr
podman exec web ip route
podman exec web cat /etc/resolv.conf
podman exec web ping -c1 10.89.0.1
hostname -I
# 进容器 netns(rootful)
nsenter -t $(podman inspect -f '{{.State.Pid}}' web) -n ss -lntp
# rootless 下看 netns
podman unshare --rootless-netns ip addr
podman unshare --rootless-netns ss -lntp
# aardvark-dns 状态
podman info --format '{{.Host.NetworkBackendInfo}}'
journalctl --user -u aardvark-dns -n 50 2>/dev/null
ps -ef | grep aardvark
| 现象 |
原因 |
处理 |
| 容器间不能按名字解析 |
不在同一自定义网络 |
--network 同一网络 |
| rootless 端口映射不通 |
pasta/slirp4netns 未装 |
apt install passt 或 slirp4netns |
| 容器无外网 |
宿主转发关闭 |
net.ipv4.ip_forward=1 |
创建网络报 subnet already in use |
网段冲突 |
--subnet 指定其他段 |
pasta 报 socket 错误 |
版本不匹配 |
升级 passt 或退回 slirp4netns |
大量 conmon 进程 |
容器多,正常 |
无需处理,或改用 Quadlet 减少探测容器 |
13. Compose
13.1 三种 provider
| provider |
说明 |
适合 |
podman compose(内置调用外部) |
5.x 起为包装器,自动找 docker-compose 或 podman-compose |
通用 |
podman-compose(Python 独立项目) |
独立实现,功能略少于 docker compose |
无 docker-compose 时 |
docker-compose(v2 二进制) |
通过 DOCKER_HOST 指向 podman socket,兼容性最好 |
复杂 compose 文件 |
# 检查实际调用了谁
podman compose version
# 安装 provider
dnf install -y podman-compose
# 或
pip install podman-compose
# 用 docker compose v2 作为后端(兼容性最佳)
dnf install -y docker-compose-plugin # 只拿二进制,不装 docker
systemctl --user enable --now podman.socket
export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
docker compose up -d
13.2 常用命令
podman compose up -d
podman compose up -d --build
podman compose down
podman compose down -v
podman compose ps
podman compose logs -f api
podman compose exec api sh
podman compose build
podman compose pull
podman compose config
podman compose restart api
podman compose run --rm migrate
13.3 兼容性注意事项
| 项 |
说明 |
deploy.resources.limits |
podman-compose 部分版本忽略;用 mem_limit/cpus 兼容写法 |
depends_on.condition |
podman-compose 支持不完整,用 --no-deps 或启动脚本兜底 |
container_name |
支持 |
profiles |
5.x 的 podman-compose 支持 |
secrets |
支持,落到 podman secret |
| 网络命名 |
项目名会被加前缀,跨项目访问需显式 name: |
build.args |
支持,但 --build-arg 传递需注意 |
# 兼容性更好的写法
services:
api:
image: harbor.internal/web/api:1.0.0
mem_limit: 2g
cpus: 2.0
restart: unless-stopped
13.4 开机自启(Compose 项目)
# 生成 systemd 单元(一次性,之后由 systemd 管理)
cat >/etc/systemd/system/webstack.service <<'EOF'
[Unit]
Description=WebStack (podman compose)
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/webstack
Environment=XDG_RUNTIME_DIR=/run/user/0
ExecStart=/usr/bin/podman compose up -d
ExecStop=/usr/bin/podman compose down
TimeoutStartSec=600
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable --now webstack
更推荐把每个服务转成 Quadlet,逐个管理,依赖和重启策略更清晰。
14. 自动更新与镜像签名
14.1 auto-update
# 1) 容器带更新标记(三种取值)
podman run -d --name web \
--label io.containers.autoupdate=registry \
docker.io/library/nginx:1.25
# registry:按 registry 中的 tag 更新
# local :按本地镜像更新(配合 build 流程)
# 2) 手动检查
podman auto-update --dry-run
podman auto-update
# 3) 定时执行
systemctl enable --now podman-auto-update.timer
systemctl list-timers podman-auto-update.timer
journalctl -u podman-auto-update -n 50
# 4) Quadlet 中开启
# AutoUpdate=registry
# 5) 回滚(auto-update 会保留旧镜像)
podman auto-update --rollback
# 查看上次更新时间与状态
podman inspect web -f '{{.ImageName}} {{index .Labels "io.containers.autoupdate"}}'
podman images --filter label=io.containers.autoupdate=registry
注意:auto-update 只对运行中且带 label 的容器生效;更新是「建新容器 + 删旧容器」,所以容器内状态会丢失。有状态服务(数据库)不要开。
14.2 镜像签名
# 生成密钥
skopeo generate-sigstore-key --output-prefix mykey
# 产出 mykey.private / mykey.pub
# 签名(推送到仓库前后都可)
podman image sign --sign-by mykey@example.com docker://harbor.internal/web/app:1.0.0
# 验证策略 /etc/containers/policy.json
cat >/etc/containers/policy.json <<'EOF'
{
"default": [{ "type": "reject" }],
"transports": {
"docker": {
"harbor.internal": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "/etc/pki/containers/mykey.pub"
}
]
},
"containers-storage": [{ "type": "insecureAcceptAnything" }]
}
}
EOF
# 之后拉未签名镜像会失败(这是期望行为)
podman pull harbor.internal/web/app:1.0.0
14.3 漏洞扫描
# 用 trivy 扫镜像
podman run --rm -v /var/run/podman/podman.sock:/var/run/docker.sock \
aquasec/trivy image harbor.internal/web/app:1.0.0
# 或导出为 oci 归档再扫
podman save --format oci-archive -o app.tar app:1.0.0
podman run --rm -v $PWD:/scan aquasec/trivy image --input /scan/app.tar
15. Podman Machine
15.1 用途
macOS / Windows 上 Podman 需要一台 Linux 虚拟机(内核依赖 namespace/cgroup)。podman machine 管理这台 VM。
# 初始化
podman machine init
podman machine init --cpus 4 --memory 8192 --disk-size 100 vm2
podman machine init --rootful # 默认 rootful 模式
podman machine init --image-path ./fedora-coreos.qcow2 # 离线
# 生命周期
podman machine list
podman machine start
podman machine start vm2
podman machine stop
podman machine ssh
podman machine inspect
podman machine rm -f
# 升级 VM 内的 OS(6.0+)
podman machine os update
# 重置(5.0+)
podman machine reset
15.2 在 VM 内操作
podman machine ssh
# 进入后就是普通 Linux
sudo dnf install -y nginx
15.3 与 k8s 集成
# 起一个单节点 k8s(需要 kind/minikube 或 k3s)
podman machine ssh
# 在 VM 内执行 k3s 安装脚本
15.4 常见问题
| 现象 |
原因 |
处理 |
machine start 卡住 |
虚拟化未开 / 端口冲突 |
检查 BIOS 虚拟化、podman machine rm 重建 |
| 镜像拉取慢 |
VM 内 DNS/代理 |
podman machine ssh 后配 registries.conf |
| 磁盘满 |
VM 默认 100G 且不自动回收 |
podman system prune -a,或重建更大的 VM |
| 文件共享 |
macOS 需显式挂载 |
--volume 在 podman machine init 时配 |
Error: vm already exists |
名字冲突 |
podman machine list,改名或删除 |
16. 与 Docker 的差异与迁移
16.1 命令对照(完整)
| 操作 |
Docker |
Podman |
| 运行 |
docker run |
podman run |
| 构建 |
docker build |
podman build |
| 拉取/推送 |
docker pull/push |
podman pull/push |
| 列表 |
docker ps/images |
podman ps/images |
| 日志 |
docker logs |
podman logs |
| 执行 |
docker exec |
podman exec |
| 检查 |
docker inspect |
podman inspect |
| 统计 |
docker stats |
podman stats |
| 事件 |
docker events |
podman events |
| 系统信息 |
docker info |
podman info |
| 清理 |
docker system prune |
podman system prune |
| 保存/加载 |
docker save/load |
podman save/load |
| 导出/导入 |
docker export/import |
podman export/import |
| 登录 |
docker login |
podman login |
| 网络列表 |
docker network ls |
podman network ls |
| 卷列表 |
docker volume ls |
podman volume ls |
| 编排 |
docker compose |
podman compose |
| 集群 |
docker swarm |
无(用 k8s / kube play) |
| 生成 k8s |
无 |
podman kube generate |
| 跑 k8s yaml |
无 |
podman kube play |
| 服务化 |
--restart |
Quadlet |
| 远程 API |
常驻 2375/2376 |
podman system service |
16.2 行为差异(会踩坑的)
| 差异点 |
Docker |
Podman |
| 短名解析 |
默认 docker.io |
按 unqualified-search-registries,可能交互提示 |
| 代理环境变量 |
不自动注入 |
自动注入宿主代理(可用 --http-proxy=false 关) |
--restart 实现 |
daemon 内建 |
依赖 podman-restart.service |
容器内 /etc/hosts |
固定格式 |
可用 --hosts-file 自定义(5.4+) |
--privileged + --device |
都生效 |
5.2 起都生效(此前忽略 --device) |
| 默认网络名 |
bridge |
podman |
| 默认 DNS |
内嵌 127.0.0.11 |
aardvark-dns |
| 存储路径 |
/var/lib/docker |
/var/lib/containers/storage(rootful)/ ~/.local/share/containers/storage(rootless) |
| 镜像存储格式 |
graphdriver / containerd store |
containers/storage |
| 容器父进程 |
containerd-shim |
conmon |
| 无 daemon |
有 |
无(podman system service 按需起) |
16.3 从 Docker 迁移
# 步骤 1:镜像迁移(最稳的是走 registry)
# 在 Docker 机器上
docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' \
| xargs -I{} docker tag {} harbor.internal/mirror/{} 2>/dev/null
docker push ... # 或用 skopeo 直接搬
# 步骤 2:Podman 机器上直接拉
podman pull harbor.internal/mirror/nginx:1.25
# 步骤 3:compose 文件复用(多数场景可直接用)
podman compose -f docker-compose.yml up -d
# 步骤 4:校验
podman ps
podman compose ps
curl localhost:8080
# 步骤 5:服务化(把 compose 转 Quadlet,逐个迁移)
# 无 registry 时的离线迁移:导出 oci 归档
docker save nginx:1.25 -o nginx.tar
podman load -i nginx.tar
16.4 迁移检查清单
| 检查项 |
说明 |
| 短名镜像 |
全部改全名,否则脚本可能卡在交互选择 |
docker.sock 依赖 |
挂 socket 的工具(watchtower、portainer agent)在 Podman 上要换方案 |
--restart 语义 |
需额外 enable podman-restart.service 或改 Quadlet |
| 端口 < 1024 |
rootless 需调 ip_unprivileged_port_start |
| 卷权限 |
rootless 下用 --userns=keep-id 或 podman unshare chown |
| SELinux |
加 :Z 标签 |
| 网络名 |
默认网络从 bridge 变 podman,compose 里若硬编码需改 |
| 健康检查 |
--health-* 支持完整,但 HEALTHCHECK 镜像指令在部分旧版本不生效 |
| BuildKit 特性 |
--mount=type=secret/cache 需 buildah 1.4x+,且语法略有差异 |
| API 客户端 |
用 podman system service 暴露 socket,路径不同 |
17. 日志与监控
17.1 日志驱动
| 驱动 |
说明 |
podman logs |
journald(默认 rootful) |
进 systemd journal |
支持 |
k8s-file(默认 rootless) |
写 JSON 文件到 runroot |
支持 |
passthrough |
直接透传到 stdout |
不支持 |
none |
不记录 |
不支持 |
podman run --log-driver journald --name web nginx:1.25
podman logs -f web
podman logs --since 10m web
podman logs --tail 100 web
# journald 下按容器查
journalctl CONTAINER_NAME=web --since today
journalctl -u container-web.service -f # Quadlet
# k8s-file 的日志路径
podman inspect web -f '{{.HostConfig.LogConfig.Path}}'
17.2 事件
podman events
podman events --filter container=web
podman events --filter event=die
podman events --format json
podman events --since 1h
# 事件写入 journald
journalctl -t podman --since today
17.3 指标
# Podman 无内建 Prometheus exporter,两种做法:
# 1) 用 podman-exporter(社区)
podman run -d --name podman-exporter \
--privileged -p 9882:9882 \
-v /run/podman/podman.sock:/run/podman/podman.sock:ro \
quay.io/navidys/prometheus-podman-exporter
curl -s localhost:9882/metrics | head
# 2) cAdvisor(rootful 场景)
podman run -d --name cadvisor --privileged \
-p 8081:8080 \
-v /:/rootfs:ro -v /var/run:/var/run:ro -v /sys:/sys:ro \
-v /var/lib/containers/storage:/var/lib/containers/storage:ro \
gcr.io/cadvisor/cadvisor:v0.49.1
17.4 巡检脚本
#!/usr/bin/env bash
set -uo pipefail
echo "===== $(date '+%F %T') podman check ====="
podman info >/dev/null || echo "!! podman 异常"
echo "--- 存储"; podman system df
echo "--- 非运行容器"; podman ps -a --filter status=exited --format '{{.Names}}\t{{.Status}}'
echo "--- unhealthy"; podman ps --filter health=unhealthy --format '{{.Names}}'
echo "--- 卷"; podman volume ls --filter dangling=true
echo "--- 网络"; podman network ls
echo "--- 磁盘"; du -sh "$(podman info --format '{{.Store.GraphRoot}}')"
echo "--- conmon 进程数"; pgrep -c conmon || true
18. 备份与迁移
18.1 备份清单
| 对象 |
位置 |
方式 |
| 镜像 |
storage |
podman save / registry |
| 卷 |
~/.local/share/containers/storage/volumes |
podman volume export 或 tar |
| Quadlet 单元 |
/etc/containers/systemd/、~/.config/containers/systemd/ |
Git |
| 配置 |
/etc/containers/、~/.config/containers/ |
Git |
| 签名策略 |
policy.json |
Git |
| pod 定义 |
podman kube generate 产出 |
Git |
# 卷批量导出
for v in $(podman volume ls -q); do
podman volume export "$v" -o "/backup/volumes/$v.tar"
done
# 镜像批量导出
podman images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' \
| xargs -r podman save -m -o /backup/images.tar
# pod 定义
podman kube generate webpod > /backup/webpod.yaml
18.2 迁移到新机器
# 旧机
podman images --format '{{.Repository}}:{{.Tag}}' > /tmp/images.txt
podman save -m -i /tmp/images.txt -o /backup/images.tar
for v in $(podman volume ls -q); do podman volume export "$v" -o "/backup/$v.tar"; done
tar czf /backup/quadlet.tgz /etc/containers/systemd ~/.config/containers/systemd 2>/dev/null
# 新机
podman load -i /backup/images.tar
podman volume create app-data && podman volume import app-data /backup/app-data.tar
tar xzf /backup/quadlet.tgz -C /
systemctl daemon-reload && systemctl start web.service
18.3 版本升级
# 升级前:备份 storage 与配置
tar czf /backup/podman-storage-$(date +%F).tar.gz \
$(podman info --format '{{.Store.GraphRoot}}')
cp -a /etc/containers /backup/containers-etc
# RHEL
dnf update -y podman buildah skopeo
# 5.8 → 6.0 特别注意
# - BoltDB 自动迁移 SQLite(首次运行触发)
# - 若迁移失败:podman system migrate --migrate-db
podman system migrate --migrate-db
# 验证
podman info
podman ps -a
podman images
podman run --rm hello-world
19. 卸载与清理
19.1 标准卸载
# 1) 停容器与 pod
podman ps -aq | xargs -r podman stop
podman pod ps -q | xargs -r podman pod rm -f
podman ps -aq | xargs -r podman rm -f
# 2) 停 systemd 相关(Quadlet 生成的服务)
systemctl --user stop 'container-*' 2>/dev/null
systemctl stop 'container-*' 2>/dev/null
systemctl --user disable --now podman.socket podman-restart.service 2>/dev/null
systemctl --user disable --now podman-auto-update.timer 2>/dev/null
# 3) 清数据
podman system prune -a --volumes -f
rm -rf /var/lib/containers
rm -rf ~/.local/share/containers
rm -rf ${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/containers
# 4) 卸载包
dnf remove -y podman podman-docker podman-compose buildah skopeo \
netavark aardvark-dns containers-common
# Ubuntu
# apt purge -y podman podman-compose buildah skopeo
# 5) 清配置
rm -rf /etc/containers
rm -rf ~/.config/containers
rm -rf ~/.docker 2>/dev/null # 如果共用过凭据
rm -f /etc/sysctl.d/99-podman.conf
rm -f /etc/modules-load.d/podman.conf
# 6) 清 Quadlet 单元
rm -rf /etc/containers/systemd
rm -rf ~/.config/containers/systemd
systemctl daemon-reload
systemctl --user daemon-reload
# 7) 清网络残留
ip link show | grep -E 'podman|veth'
for i in $(ip -o link show | awk -F': ' '/podman|veth/{print $2}'); do ip link delete "$i"; done
# 8) 验证
which podman || echo "已卸载"
systemctl list-unit-files | grep -i podman
ls /etc/containers 2>/dev/null
19.2 只清数据(重置)
podman system reset # 删所有镜像/容器/卷/网络/存储
podman system reset -f # 不确认
# 注意:会删整个 storage 目录,不可恢复
19.3 卸载后残留检查
| 检查 |
命令 |
| 进程 |
ps -ef | grep -E 'conmon|podman' |
| 单元 |
systemctl list-unit-files | grep -i podman |
| 目录 |
ls /var/lib/containers ~/.local/share/containers |
| 网桥 |
ip link | grep -E 'podman|veth' |
| 配置 |
ls /etc/containers |
| socket |
ls /run/user/$(id -u)/podman |
| subuid 残留 |
grep $USER /etc/subuid /etc/subgid(可保留,无害) |
20. 故障速查
20.1 rootless 相关(最高频)
| 现象 |
排查 |
处理 |
cannot clone: Operation not permitted |
sysctl kernel.unprivileged_userns_clone |
置 1;装 uidmap |
newuidmap: not found |
which newuidmap |
apt install uidmap / dnf install shadow-utils |
cannot set up namespace using "/usr/bin/newuidmap" |
/etc/subuid 无记录 |
usermod --add-subuids ... |
Error: creating container storage: ... permission denied |
subuid 区间被占用/冲突 |
检查 /etc/subuid 是否重复 |
--memory 不生效 |
cgroup v2 未委派 |
Delegate=yes(见 5.3) |
podman stats 无数据 |
cgroup 未委派 |
同上 |
| 退出登录容器就停 |
未开 linger |
loginctl enable-linger $USER |
| 绑 80 端口失败 |
低端口限制 |
ip_unprivileged_port_start=80 |
| 容器内 uid 无用户名 |
无 /etc/passwd 条目 |
加 --passwd |
20.2 存储
| 现象 |
排查 |
处理 |
no space left on device |
podman system df |
podman system prune -a |
overlay: invalid argument(rootless) |
内核 < 5.11 |
改 fuse-overlayfs |
layer not known |
storage 损坏 |
podman system reset(先备份) |
| 卷权限拒绝 |
podman unshare ls -ln |
podman unshare chown |
database ... locked |
并发操作 |
等待或 podman system migrate |
| 5.8 升级后启动慢 |
BoltDB → SQLite 迁移 |
等首次迁移完成;失败用 --migrate-db |
20.3 网络
| 现象 |
排查 |
处理 |
| 端口映射不通 |
podman port、ss -lntp |
检查 pasta/slirp4netns 是否安装 |
| 容器无 DNS |
cat /etc/resolv.conf |
检查 aardvark-dns 进程 |
| 容器间不能按名访问 |
podman network inspect |
同一网络 |
netavark: ... subnet |
网段冲突 |
--subnet |
| rootless host 网络异常 |
podman unshare --rootless-netns ip addr |
换 pasta/slirp4netns 实现 |
pasta 报错 |
pasta --version |
升级 passt 或退回 slirp4netns |
20.4 Quadlet 与 systemd
| 现象 |
排查 |
处理 |
Unit not found |
ls /etc/containers/systemd |
路径/后缀/daemon-reload |
| 服务起但容器退出 |
journalctl -u x -n 50 |
前台进程问题 |
conmon 持续重启 |
podman logs |
应用崩溃 |
| 用户级不生效 |
systemctl --user status |
XDG_RUNTIME_DIR、linger |
| 改了配置不生效 |
systemctl cat x.service |
daemon-reload + restart |
Dryrun 报语法错 |
/usr/libexec/podman/quadlet -dryrun -f x |
按提示改键名 |
20.5 通用
podman info # 第一步永远是它
podman system check # 存储一致性检查
podman --log-level=debug run ... # 详细日志
podman events --since 10m
journalctl --user -n 100
journalctl -t podman -n 100
21. 附录
21.1 关键路径
| 项 |
rootful |
rootless |
| 存储 |
/var/lib/containers/storage |
~/.local/share/containers/storage |
| 运行态 |
/run/containers/storage |
/run/user/<uid>/containers |
| 系统配置 |
/etc/containers/ |
~/.config/containers/ |
| Quadlet(系统) |
/etc/containers/systemd/ |
~/.config/containers/systemd/ |
| 凭据 |
/run/containers/0/auth.json |
~/.config/containers/auth.json |
| socket |
/run/podman/podman.sock |
/run/user/<uid>/podman/podman.sock |
| systemd 单元(生成) |
/run/systemd/generator/ |
同左(user 实例) |
| subuid |
/etc/subuid |
同 |
| subgid |
/etc/subgid |
同 |
21.2 常用格式化输出
podman ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
podman ps --format '{{.Names}} {{.Labels.env}}'
podman images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}'
podman volume ls --format '{{.Name}}'
podman network ls --format '{{.Name}} {{.Driver}}'
podman pod ps --format 'table {{.Name}}\t{{.Status}}\t{{.NumContainers}}'
podman stats --no-stream --format '{{.Name}} {{.CPUPerc}} {{.MemUsage}}'
podman system df --format '{{.Type}} {{.Total}} {{.Size}} {{.Reclaimable}}'
podman inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(podman ps -q)
21.3 一键排障
# 容器起不来
podman ps -a | grep <name>
podman logs --tail 100 <name>
podman inspect <name> --format '{{json .State}}' | jq
podman inspect <name> --format '{{json .Config}}' | jq
podman events --since 10m --filter container=<name>
# rootless 环境
podman info --format '{{.Host.Security.Rootless}} {{.Store.GraphRoot}}'
podman unshare cat /proc/self/uid_map
loginctl show-user $USER | grep Linger
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.controllers
# Quadlet 服务
systemctl cat web.service
journalctl -u web.service -n 100 --no-pager
/usr/libexec/podman/quadlet -dryrun -f /etc/containers/systemd/web.container
21.4 与其他手册的配合
- 宿主机系统(包管理、SELinux、防火墙):
CentOS-7-10-常规运维指令手册.md / Ubuntu-常用运维指令手册.md
- Docker 对照与迁移:
Docker-常用运维指令手册.md
- 集群编排:
Kubernetes-常用运维指令手册.md
- 私有仓库:
Harbor-镜像仓库运维指令手册.md
- 数据库容器化:
MySQL-常用运维指令手册.md
一句话总结:Podman 的难点不在命令(和 Docker 几乎一样),而在三处——rootless 的 UID 映射与 cgroup 委派、SELinux 标签、systemd/Quadlet 的服务化方式。这三块顺了,剩下的都是 Docker 经验直接迁移。