熟悉容器技术的朋友,这两年应该没少听到 Podman 这个名字。我大概从 2021 年开始在生产环境里小规模试用它,到现在已经习惯了把 Podman 作为主力容器引擎,Docker 反而降级成兼容性测试工具。如果你也受够了 Docker daemon 偶尔抽风导致所有容器一起挂掉,或者在多用户服务器上被 root 权限问题折腾得头疼,那么这篇内容就是为你准备的。我会从 Podman 最核心的设计思路讲起,然后完整走一遍在 Linux 服务器上同时安装 Docker 和 Podman、切换使用、配置镜像加速和网络参数的过程,最后把这一两年里踩过的一些坑整理成问题清单。无论你是刚接触容器的新手,还是准备迁移的老手,按着这篇文章的操作走一遍,基本能平稳落地。
1. Podman 到底是什么,解决什么问题
1.1 没有守护进程的容器引擎
很多人第一次接触 Podman 都会问一句话:它和 Docker 到底有什么区别?最直白的回答是,Docker 靠一个常驻后台的 daemon 进程管理所有容器,而 Podman 没有这个中心化的 daemon。你在命令行敲下 podman run 的时候,Podman 会直接用 fork 的方式启动一个容器进程,所有操作都由当前用户自己完成。这个差异带来的实际好处,我举一个非常典型的场景:服务器上的 Docker daemon 因为日志暴涨或者网络插件异常挂掉了,你累死累活地重启服务,然后发现原本跑着的十几个容器全部停掉了。这种情况但凡经历过一次就知道有多痛。而 Podman 每个容器进程都是独立的,互不依赖守护进程,某个容器崩溃不会拖累别的容器,系统重启之后容器也能通过 systemd 服务自动恢复,稳定性上确实踏实很多。
还有一个容易被忽视的点是安全模型。Docker 的操作基本都要通过 daemon 来完成,这个 daemon 通常是 root 权限运行的,等于你每次执行 docker ps 都是在向一个特权进程请求服务。Podman 的 rootless 模式让普通用户也能直接管理自己的容器,不需要给用户 sudo 权限。这种设计在多租户服务器上特别实用,团队里每个人各自创建自己的容器,互不干扰,也不会出现某人删了别人的容器这种事故。
1.2 与 Docker 的兼容性和差异化定位
我见过不少人听到“Podman 兼容 Docker”这句话后,以为 Podman 能直接无缝替换 Docker,然后一执行 docker-compose up 就发现不对劲。这里得把兼容性的边界说清楚。Podman 的命令行接口和 Docker 高度一致,run、ps、exec、logs、build 这些核心操作你几乎感觉不到差别,甚至可以直接通过设置环境变量 export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock,让 Docker CLI 去连接 Podman 的 socket。我自己实测过,大部分 docker 命令都能正常执行,很多 CI 工具也能直接复用。
但说到 docker-compose,完整替代还差一点火候。Podman 官方推荐用 podman-compose 或者 Quadlet 方案,虽然日常的 volume、network、环境变量配置能兼容大部分场景,但一些冷门的 compose 语法还是有差异的。所以我的建议是:个人开发环境、单容器部署、生产服务托管,直接用 Podman;但如果你深度依赖 Docker Compose,并且在团队协作中对 compose 文件的通用性要求很高,那就保留 Docker 作为补充工具,互相配合使用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 安装 Docker 和 Podman 的完整实操
2.1 系统准备与安装步骤
我这次演示用的是一台全新安装的 Ubuntu 22.04 LTS 服务器,干净的系统最适合用来梳理完整的安装流程。无论你是准备安装 Docker 还是 Podman,第一步都是一样的:更新系统软件包索引,确保依赖环境没问题。
bash复制sudo apt update && sudo apt upgrade -y
接下来安装 Podman。Ubuntu 22.04 官方软件源里其实已经带了 Podman,不过版本偏旧,我建议用一种更省心也比较稳妥的方式,直接用官方源安装,确保拿到的是当前稳定版:
bash复制sudo apt install -y podman
podman --version
我在另一台 CentOS 9 的机器上用的是 dnf install podman,步骤差不多,这里就不展开多写了。安装完先不要急着创建容器,建议马上做两件事:第一,检查 containers.conf 配置文件中 cgroup manager 是否设置正确,我这边用的是 systemd,如果你的系统初始化方式不同,后面跑 systemd 容器时可能会报错;第二,编辑 /etc/containers/registries.conf,配置镜像源,这块我放到下一节详细讲。
然后是 Docker 的安装。依然是在同一台 Ubuntu 服务器上操作,使用 Docker 官方仓库安装可以拿到最新的稳定版本:
bash复制sudo apt install -y ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
docker --version
安装过程中有个容易被忽略的细节:Docker 默认会把 docker 这个命令放在 /usr/bin 里,而 Podman 也会在同一个目录生成同名命令吗?实际上并不会,Podman 的命令名是 podman,两者可以共存,命令行层面没有冲突,唯一需要注意的是端口占用问题。我记得第一次在同一台机器上同时启用 Docker 和 Podman 的时候,因为两个引擎的网络配置会各自动态分配端口,万一出现端口被占用,你会看到 bind 失败的错误,这时候用 ss -tlnp 查一下占用情况就能定位到问题。
2.2 双引擎共存时的网络隔离与注意事项
Docker 默认使用 docker0 网桥,Podman 在 rootful 模式下默认使用 cni-podman0 网桥。两个网桥的网段默认都是 172.17.0.0/16 这块区域,如果两个引擎同时启动,非常容易出现 IP 地址冲突。这不是我危言耸听,第一次踩到这个坑的时候,我在 Podman 容器里访问数据库,网络时通时不通,排查了半天才发现是跨引擎的网段冲突。
解决方法是把其中一个引擎的默认网段改掉。以 Docker 为例,编辑 /etc/docker/daemon.json:
json复制{
"default-address-pools": [
{
"base": "10.10.0.0/16",
"size": 24
}
]
}
然后重启 Docker:
bash复制sudo systemctl restart docker
Podman 这边可以通过修改 /etc/containers/containers.conf 中的 network_cidr 参数来调整,不过在最新的版本里,Podman 4.x 默认用的已经是 netavark 网络栈了,配置方式略有不同,还是建议先用上面的 default-address-pools 把 Docker 的网段改走,留出 Podman 默认的地址段。只要这两个引擎的网络互不干扰,其他方面共存基本没有大问题。
还有一点:如果一个容器需要用 --network host 模式,那么无论哪套引擎都会直接使用宿主机的网络栈,这种情况下,你不可能同时让 Docker 和 Podman 里的两个容器都监听 80 端口,这取决于业务上如何处理端口复用,跟引擎本身无关。我的经验是,生产环境尽量给不同项目分配不同的非标准端口,避免这种不必要的冲突。
3. Podman 核心配置与拉取镜像的加速实战
3.1 镜像源与加速配置方法
这个环节是我认为“Podman 配置代理”这个热词背后真正大家想解决的问题。新装的 Podman 默认从 Docker Hub 拉镜像,但在国内网络环境下,直连 Docker Hub 的速度和稳定性都很难让人满意。这时候就需要在 /etc/containers/registries.conf 里配置镜像加速器。
我直接贴一份我自己在用的配置文件核心部分:
toml复制unqualified-search-registries = ["docker.io"]
[[registry]]
prefix = "docker.io"
location = "docker.io"
[[registry.mirror]]
location = "mirror.gcr.io"
这里解释一下配置逻辑。unqualified-search-registries 的作用是,当你执行 podman pull nginx 这样的命令没有指定完整仓库地址的时候,Podman 会在这个列表里补齐镜像源。[[registry]] 表定义的是镜像源别名规则,prefix = "docker.io" 表示匹配所有 docker.io 开头的镜像,location 是实际拉取的地址。[[registry.mirror]] 用于配置镜像源镜像源,也就是所谓加速器,Podman 会优先尝试 mirror 里配置的地址,失败后自动回退到原地址。
配置完成后,不要忘记验证一下镜像源是否生效:
bash复制podman info | grep -A 10 "registries"
如果看到 mirror.gcr.io 出现在输出里,说明配置已经生效。实测下来,配置完加速之后,拉取一个 nginx 镜像的速度从原来的几分钟降到了十几秒,体感非常明显。
3.2 通过配置文件设置代理参数
有些服务器不在公网直连环境,需要通过内部代理服务器访问外网。这个时候,容器引擎本身的拉取流量也需要走代理。Podman 设置代理有两种做法,一种是通过环境变量,一种是通过配置文件。
环境变量的方式很简单:
bash复制export HTTP_PROXY=http://your-proxy-ip:port
export HTTPS_PROXY=http://your-proxy-ip:port
export NO_PROXY=localhost,127.0.0.1,10.0.0.0/8
podman pull nginx
但是这种方法只对当前终端会话有效,重启终端就失效了。更推荐的做法是直接写到用户级或系统级的配置里。在 /etc/containers/containers.conf 中,找到 [engine] 段落,加入这些行:
toml复制[engine]
env = [
"HTTP_PROXY=http://your-proxy-ip:port",
"HTTPS_PROXY=http://your-proxy-ip:port",
"NO_PROXY=localhost,127.0.0.1,10.0.0.0/8"
]
这样设置后,无论谁在系统上执行 Podman 命令,拉取镜像时都会自动走代理。另外一个容易遗漏的点是:这里的代理只管容器引擎本身拉取镜像用的网络流量,和容器内部应用的代理是两码事。如果你需要让容器内的进程访问外部网络,需要在 podman run 的时候通过 --env 把代理参数传进去,或者用更优雅的容器内 systemd 环境变量方式配置。
我自己做过一个对比测试:同样配置代理参数,通过配置文件方式设置后,podman pull 和 podman build 都稳定走代理,拉镜像速度非常稳,不会像环境变量那样时有波动。我推荐生产环境用配置文件方式,一是持久化,二是不容易漏配置。
3.3 rootless 与 rootful 模式的选择
Podman 支持 rootless 和 rootful 两种执行模式。默认情况下,普通用户执行 podman 命令,走的就是 rootless 模式,不需要任何 sudo。这对安全性和权限隔离都有很大好处,但有个前提条件是系统必须开启用户命名空间,并且非 root 用户对相关目录可写。安装完成后建议马上执行一次全功能测试:
bash复制podman run hello-world
如果提示失败,先别急着骂系统烂,大概率是 cgroup 或 fuse-overlayfs 的问题。我曾在 CentOS 9 上遇到这种报错,最终解决的方案是安装 fuse-overlayfs 软件包,再配置 /etc/containers/storage.conf 里的挂载驱动为 fuse-overlayfs:
toml复制[storage]
driver = "fuse-overlayfs"
rootful 模式则是通过 sudo podman 执行的,所有容器以 root 用户身份运行,和 Docker 默认情况类似。这两种模式的容器数据并不互通,也就是说你在普通用户下创建的所有容器,在 root 模式下是看不到的。这一点非常重要,因为很多人第一次切到 rootful 模式时发现容器全没了,以为数据丢失了,其实就是模式之间的隔离机制,不用慌。我在实际部署中,产品环境一律用 rootless 模式,开发调试才偶尔切 rootful,这样可以尽量减少特权操作带来的风险。
4. 常见问题与排查技巧实录
4.1 容器启动失败:权限与存储驱动问题
先讲一个我遇到的高频问题:在 Ubuntu 上第一次执行 podman run,报错信息为 fork/exec /proc/self/exe: no such file or directory,或者直接提示 Error: open /dev/fuse: no such file or directory。这类问题基本都和 rootless 模式要求的权限环境有关。解决方法很简单:
bash复制sudo apt install -y fuse-overlayfs slirp4netns
sudo touch /etc/subuid /etc/subgid
usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
这里解释一下原理:rootless 容器需要把容器内的 root 用户映射到宿主机上一个无特权的普通用户,这个映射关系就是通过 /etc/subuid 和 /etc/subgid 来定义的。默认创建的普通用户往往没有分配这些 ID 段,导致容器运行失败。执行完上面的命令后,重新登录一次用户,再执行 podman run hello-world 就能正常跑了。
另一个是存储驱动问题。Ubuntu 默认的 overlay 驱动在 rootless 模式下有时候会报权限错误,这个时候把存储驱动切换成 fuse-overlayfs 就能解决。安装好 fuse-overlayfs 后,执行:
bash复制podman system reset
podman info | grep -i graphdriver
确认输出为 fuse-overlayfs,再跑容器就正常了。需要特别提醒,podman system reset 会删除所有本地容器和镜像,执行之前务必确认没有需要保留的数据,有重要容器的话记得先导出备份。
4.2 常用运维命令与健康检查
Podman 的命令和 Docker 高度相似,迁移成本很低,但有几个运维层面的小技巧值得单独拎出来讲。
查看所有容器状态,包括已经停止的容器,用:
bash复制podman ps -a
实时查看某个容器的资源占用,用:
bash复制podman stats
与 Docker 最大的区别在这里:当你想要查看一个正在运行的容器用了多少资源流量,Docker 往往要额外装插件才能实现,而 Podman 自带的 podman stats 已经包含网络流量信息,虽然数值没有 cAdvisor 那么重,但日常监控足够用了。
重启策略也需要注意。Docker 容器挂了会自动重启,需要你在启动时加 --restart always 参数,或者通过 compose 文件指定。Podman 在高版本里也支持 --restart,但是在 rootless 模式下,这个重启策略依赖用户级 systemd 服务来保证,如果你直接用普通用户跑一个带 --restart always 的容器,重启宿主后可能发现容器没有自动恢复。最靠谱的做法是用 systemd 托管容器:
bash复制podman generate systemd --name my-container --files
这会生成一个 systemd unit 文件,然后把它放到 ~/.config/systemd/user/ 目录下,启用并启动服务:
bash复制systemctl --user daemon-reload
systemctl --user enable --now container-my-container.service
别忘了还要开启用户级服务的持久化,否则通过 SSH 登录主机时 systemd user 实例不会自动启动:
bash复制sudo loginctl enable-linger $USER
这个方案我实测在 Ubuntu 22.04 和 CentOS 9 上都稳定运行,容器重启后能自动拉起,比依赖 Podman 内置的 restart 策略可靠得多。
4.3 常见问题速查表
下面这张表是我把过去一年里遇到过的典型问题整理出来的,按症状、原因和解决方法三列归类,方便你直接查阅:
| 症状 | 常见原因 | 解决方法 |
|---|---|---|
执行 podman run 报权限错误 |
未安装 fuse-overlayfs 或未配置 subuid/subgid | 安装 fuse-overlayfs,配置 /etc/subuid 和 /etc/subgid |
| 拉取镜像速度极慢 | 未配置镜像加速器 | 编辑 /etc/containers/registries.conf,配置 [[registry.mirror]] |
| rootless 容器无法映射端口 | 未安装 slirp4netns | sudo apt install slirp4netns |
| root 用户下看不到容器 | rootless 与 rootful 数据隔离 | 检查是否使用 sudo podman 执行命令 |
| 容器内无法访问外网 | 引擎代理配置只作用于拉镜像,不影响容器内应用 | 用 --env 传入代理变量,或配置容器内 systemd 环境变量 |
| 容器重启后不会自动启动 | rootless 模式下 restart 策略依赖 systemd | 使用 podman generate systemd 生成 unit 并启用 |
| 宿主机重启后镜像加速器配置失效 | 修改了系统配置文件但没有重新加载 | 执行 podman info 确认配置,必要时重启 Podman 服务 |
这个表格不是一次成型了,是踩了很多坑慢慢补全的。每次遇到新问题我都会先记下来,对比表里的原因排查一遍,解决效率提高不少。
4.4 双引擎竞争下的排错经验
当 Docker 和 Podman 同时运行在一台机器上的时候,排错复杂度会成倍增加。我遇到过最典型的案例是:一个 Nginx 容器在 Podman 里正常运行了一周,突然某天早上访问不了了,我上去一看,发现端口已经被 Docker 里的另一个 Nginx 容器抢占了。这种问题的本质在于两个引擎不会互相感知对方的端口占用情况,Docker 可以在 Podman 容器运行期间监听同一个端口,Podman 也会在 Docker 容器运行期间报告端口绑定成功,但实际网络层已经冲突。
让两个引擎互相避让的办法是让它们各自只能看到自己可以用的端口范围,但这其实不太现实。我的经验是给所有容器统一规划端口分配方案,并且使用固定端口而不是默认的随机端口。比如内部业务服务一律用 8000-8999 段的端口,数据库用 5000-5999 段,中间件用 9000-9999 段。这样即便两个引擎同时使用,端口冲突的概率也会大大降低。另外,在启动容器前先在宿主机上执行 ss -tlnp 检查端口占用情况,这已经成了我每天写进脚本的一个习惯。
4.5 为什么我最终选择 Podman 作为主力
到现在,我在生产环境部署新服务的时候,默认引擎都是 Podman。这不是说我完全抛弃 Docker,而是两者在我这里各司其职:Docker 用于跑一些对兼容性要求极高的 CI 流水线和测试环境,Podman 则承担所有常驻业务服务的托管。这套组合拳打下来,稳定性确实上了一个层级。
Podman 还有一个很省心的设计,就是它和 systemd 深度集成。前面提到过用 podman generate systemd 生成 unit 文件来托管容器,配合 loginctl enable-linger 实现开机自启,整条链路不需要额外的 supervisor 或者 orchestration 工具。对于那种规模不大、不希望引入 Kubernetes 的团队来说,这几乎是最优解了。而当以后容器规模真的上来,Podman 的 Kubernetes 兼容机制也能让你直接生成或使用 YAML 文件,迁移 Kubernetes 的路径也是平滑的。
根据我一个朋友的真实反馈,他年初把一套 Python 后端服务用 Podman 部署到三台机器上,运行三个多月零宕机,重启也没出过乱子。他用的是非常朴素的方案:静态二进制、systemd 托管、Podman 运行,一套下来非常稳。如果是用 Docker daemon 的默认方案,这种稳定性就需要额外的守护进程心跳、日志切割等一堆周边去保障了。
5. 从一个实践经验出发的最终建议
如果你问我 Podman 到底值不值得学、值不值得从 Docker 迁移过来,我的答案很简单:值得,但没必要一次性推倒重来。把环境变量的差异、镜像加速的配置、存储驱动的适配这几个基础工作做完,Podman 用起来的舒适度远超 Docker。尤其对于小团队和个人开发者,Podman 的 rootless 模式和 systemd 集成能够省下不少服务器管理的麻烦。
如果你是初学者,不要被“多了一个 Podman”吓到,它的心智负担其实比 Docker 更简单——因为它没有守护进程,没有复杂的前后端服务交互逻辑,所有能力都集中在一个二进制文件里。你可以先下载一个 Podman,在自己电脑上跑第一个 hello-world 容器,再跑一个 nginx 容器,一步步感受一下它的流程。
迁移过程中如果遇到任何上面没写到的问题,我的建议是先在 /etc/containers/ 下的配置文件里找答案,再去翻官方文档。绝大多数问题都不是 Podman 本身的问题,而是操作系统环境和网络环境的适配问题。这个经验在今天依然适用,希望你少走弯路。
